models.py
Python script, ASCII text executable
1from app import app, db, bcrypt 2import git 3from datetime import datetime 4from enum import Enum 5from PIL import Image 6from cairosvg import svg2png 7import os 8import config 9import cairosvg 10import random 11 12__all__ = [ 13"RepoAccess", 14"RepoFavourite", 15"Repo", 16"UserFollow", 17"UserNotification", 18"User", 19"Notification", 20"PostVote", 21"Post", 22"Commit", 23"PullRequest", 24] 25 26with (app.app_context()): 27class RepoAccess(db.Model): 28id = db.Column(db.Integer, primary_key=True) 29user_username = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 30repo_route = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 31access_level = db.Column(db.SmallInteger(), nullable=False) # 0 read-only, 1 read-write, 2 admin 32 33user = db.relationship("User", back_populates="repo_access") 34repo = db.relationship("Repo", back_populates="repo_access") 35 36__table_args__ = (db.UniqueConstraint("user_username", "repo_route", name="_user_repo_uc"),) 37 38def __init__(self, user, repo, level): 39self.user_username = user.username 40self.repo_route = repo.route 41self.access_level = level 42 43 44class RepoFavourite(db.Model): 45id = db.Column(db.Integer, primary_key=True) 46user_username = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 47repo_route = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 48 49notify_commit = db.Column(db.Boolean) 50 51user = db.relationship("User", back_populates="favourites") 52repo = db.relationship("Repo", back_populates="favourites") 53 54__table_args__ = (db.UniqueConstraint("user_username", "repo_route", name="_user_repo_uc1"),) 55 56def __init__(self, user, repo): 57self.user_username = user.username 58self.repo_route = repo.route 59 60 61class PostVote(db.Model): 62id = db.Column(db.Integer, primary_key=True) 63user_username = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 64post_identifier = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=False) 65vote_score = db.Column(db.SmallInteger(), nullable=False) 66 67user = db.relationship("User", back_populates="votes") 68post = db.relationship("Post", back_populates="votes") 69 70__table_args__ = (db.UniqueConstraint("user_username", "post_identifier", name="_user_post_uc"),) 71 72def __init__(self, user, post, score): 73self.user_username = user.username 74self.post_identifier = post.identifier 75self.vote_score = score 76 77 78class User(db.Model): 79username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True) 80display_name = db.Column(db.Unicode(128), unique=False, nullable=True) 81bio = db.Column(db.Unicode(16384), unique=False, nullable=True) 82password_hashed = db.Column(db.String(60), nullable=False) 83email = db.Column(db.String(254), nullable=True) 84company = db.Column(db.Unicode(64), nullable=True) 85company_url = db.Column(db.String(256), nullable=True) 86url = db.Column(db.String(256), nullable=True) 87show_mail = db.Column(db.Boolean, default=False, nullable=False) 88location = db.Column(db.Unicode(64), nullable=True) 89creation_date = db.Column(db.DateTime, default=datetime.utcnow) 90 91repositories = db.relationship("Repo", back_populates="owner") 92followers = db.relationship("UserFollow", back_populates="followed", foreign_keys="[UserFollow.followed_username]") 93follows = db.relationship("UserFollow", back_populates="follower", foreign_keys="[UserFollow.follower_username]") 94repo_access = db.relationship("RepoAccess", back_populates="user") 95votes = db.relationship("PostVote", back_populates="user") 96favourites = db.relationship("RepoFavourite", back_populates="user") 97 98commits = db.relationship("Commit", back_populates="owner") 99posts = db.relationship("Post", back_populates="owner") 100prs = db.relationship("PullRequest", back_populates="owner") 101notifications = db.relationship("UserNotification", back_populates="user") 102 103def __init__(self, username, password, email=None, display_name=None): 104self.username = username 105self.password_hashed = bcrypt.generate_password_hash(password, config.HASHING_ROUNDS).decode("utf-8") 106self.email = email 107self.display_name = display_name 108 109# Create the user's directory 110if not os.path.exists(os.path.join(config.REPOS_PATH, username)): 111os.makedirs(os.path.join(config.REPOS_PATH, username)) 112if not os.path.exists(os.path.join(config.USERDATA_PATH, username)): 113os.makedirs(os.path.join(config.USERDATA_PATH, username)) 114 115avatar_name = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH)) 116if os.path.join(config.DEFAULT_AVATARS_PATH, avatar_name).endswith(".svg"): 117cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatar_name), 118write_to="/tmp/roundabout-avatar.png") 119avatar = Image.open("/tmp/roundabout-avatar.png") 120else: 121avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatar_name)) 122avatar.thumbnail(config.AVATAR_SIZE) 123avatar.save(os.path.join(config.USERDATA_PATH, username, "avatar.png")) 124 125 126class Repo(db.Model): 127route = db.Column(db.String(98), unique=True, nullable=False, primary_key=True) 128owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 129name = db.Column(db.String(64), nullable=False) 130owner = db.relationship("User", back_populates="repositories") 131visibility = db.Column(db.SmallInteger(), nullable=False) 132info = db.Column(db.Unicode(512), nullable=True) 133url = db.Column(db.String(256), nullable=True) 134creation_date = db.Column(db.DateTime, default=datetime.utcnow) 135 136default_branch = db.Column(db.String(64), nullable=True, default="") 137 138commits = db.relationship("Commit", back_populates="repo") 139posts = db.relationship("Post", back_populates="repo") 140repo_access = db.relationship("RepoAccess", back_populates="repo") 141favourites = db.relationship("RepoFavourite", back_populates="repo") 142heads = db.relationship("PullRequest", back_populates="head", foreign_keys="[PullRequest.head_route]") 143bases = db.relationship("PullRequest", back_populates="base", foreign_keys="[PullRequest.base_route]") 144 145last_post_id = db.Column(db.Integer, nullable=False, default=0) 146 147def __init__(self, owner, name, visibility): 148self.route = f"/{owner.username}/{name}" 149self.name = name 150self.owner_name = owner.username 151self.owner = owner 152self.visibility = visibility 153 154# Add the owner as an admin 155repo_access = RepoAccess(owner, self, 2) 156db.session.add(repo_access) 157 158 159class Commit(db.Model): 160identifier = db.Column(db.String(227), unique=True, nullable=False, primary_key=True) 161sha = db.Column(db.String(128), nullable=False) 162repo_name = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 163owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 164owner_identity = db.Column(db.String(321)) 165receive_date = db.Column(db.DateTime, default=datetime.now) 166author_date = db.Column(db.DateTime) 167message = db.Column(db.UnicodeText) 168repo = db.relationship("Repo", back_populates="commits") 169owner = db.relationship("User", back_populates="commits") 170 171def __init__(self, sha, owner, repo, date, message, owner_identity): 172self.identifier = f"{repo.route}/{sha}" 173self.sha = sha 174self.repo_name = repo.route 175self.repo = repo 176self.owner_name = owner.username 177self.owner = owner 178self.author_date = datetime.fromtimestamp(int(date)) 179self.message = message 180self.owner_identity = owner_identity 181 182 183class Post(db.Model): 184identifier = db.Column(db.String(109), unique=True, nullable=False, primary_key=True) 185number = db.Column(db.Integer, nullable=False) 186repo_name = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 187owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 188votes = db.relationship("PostVote", back_populates="post") 189vote_sum = db.Column(db.Integer, nullable=False, default=0) 190 191parent_id = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True) 192state = db.Column(db.SmallInteger, nullable=True, default=1) 193 194date = db.Column(db.DateTime, default=datetime.now) 195last_updated = db.Column(db.DateTime, default=datetime.now) 196subject = db.Column(db.Unicode(384)) 197message = db.Column(db.UnicodeText) 198repo = db.relationship("Repo", back_populates="posts") 199owner = db.relationship("User", back_populates="posts") 200parent = db.relationship("Post", back_populates="children", remote_side="Post.identifier") 201children = db.relationship("Post", back_populates="parent", remote_side="Post.parent_id") 202 203def __init__(self, owner, repo, parent, subject, message): 204self.identifier = f"{repo.route}/{repo.last_post_id}" 205self.number = repo.last_post_id 206self.repo_name = repo.route 207self.repo = repo 208self.owner_name = owner.username 209self.owner = owner 210self.subject = subject 211self.message = message 212self.parent = parent 213repo.last_post_id += 1 214 215def update_date(self): 216self.last_updated = datetime.now() 217with db.session.no_autoflush: 218if self.parent is not None: 219self.parent.update_date() 220 221 222class UserNotification(db.Model): 223id = db.Column(db.Integer, primary_key=True) 224user_username = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 225notification_id = db.Column(db.BigInteger, db.ForeignKey("notification.id")) 226attention_level = db.Column(db.SmallInteger, nullable=False) # 0 is read 227read_time = db.Column(db.DateTime, nullable=True) 228 229user = db.relationship("User", back_populates="notifications") 230notification = db.relationship("Notification", back_populates="notifications") 231 232__table_args__ = (db.UniqueConstraint("user_username", "notification_id", name="_user_notification_uc"),) 233 234def __init__(self, user, notification, level): 235self.user_username = user.username 236self.notification_id = notification.id 237self.attention_level = level 238 239def read(self): 240self.read_time = datetime.utcnow 241self.attention_level = 0 242 243 244class UserFollow(db.Model): 245id = db.Column(db.Integer, primary_key=True) 246follower_username = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False) 247followed_username = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False) 248 249follower = db.relationship("User", back_populates="followers", foreign_keys=[follower_username]) 250followed = db.relationship("User", back_populates="follows", foreign_keys=[followed_username]) 251 252def __init__(self, follower_username, followed_username): 253self.follower_username = follower_username 254self.followed_username = followed_username 255 256 257class Notification(db.Model): 258id = db.Column(db.BigInteger, primary_key=True, autoincrement=True) 259data = db.Column(db.dialects.postgresql.JSONB, nullable=False, default={}) 260notifications = db.relationship("UserNotification", back_populates="notification") 261timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now) 262 263def __init__(self, json): 264self.data = json 265 266 267class PullRequest(db.Model): 268id = db.Column(db.BigInteger, primary_key=True, autoincrement=True) 269head_route = db.Column(db.String(98), db.ForeignKey("repo.route", ondelete="CASCADE"), nullable=False) 270base_route = db.Column(db.String(98), db.ForeignKey("repo.route", ondelete="CASCADE"), nullable=False) 271owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 272state = db.Column(db.SmallInteger, nullable=False, default=0) # 0 pending, 1 merged, 2 rejected 273 274head = db.relationship("Repo", back_populates="heads", foreign_keys=[head_route]) 275base = db.relationship("Repo", back_populates="bases", foreign_keys=[base_route]) 276 277head_branch = db.Column(db.String(64), nullable=False) 278base_branch = db.Column(db.String(64), nullable=False) 279 280owner = db.relationship("User", back_populates="prs") 281timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now) 282 283def __init__(self, head, head_branch, base, base_branch, owner): 284self.head = head 285self.base = base 286self.head_branch = head_branch 287self.base_branch = base_branch 288self.owner = owner 289