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) 192root_id = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True) 193state = db.Column(db.SmallInteger, nullable=True, default=1) 194 195date = db.Column(db.DateTime, default=datetime.now) 196last_updated = db.Column(db.DateTime, default=datetime.now) 197subject = db.Column(db.Unicode(384)) 198message = db.Column(db.UnicodeText) 199repo = db.relationship("Repo", back_populates="posts") 200owner = db.relationship("User", back_populates="posts") 201parent = db.relationship("Post", back_populates="children", 202primaryjoin="Post.parent_id==Post.identifier", 203foreign_keys="[Post.parent_id]", remote_side="Post.identifier") 204root = db.relationship("Post", 205primaryjoin="Post.root_id==Post.identifier", 206foreign_keys="[Post.root_id]", remote_side="Post.identifier") 207children = db.relationship("Post", 208remote_side="Post.parent_id", 209primaryjoin="Post.identifier==Post.parent_id", 210foreign_keys="[Post.parent_id]") 211 212def __init__(self, owner, repo, parent, subject, message): 213self.identifier = f"{repo.route}/{repo.last_post_id}" 214self.number = repo.last_post_id 215self.repo_name = repo.route 216self.repo = repo 217self.owner_name = owner.username 218self.owner = owner 219self.subject = subject 220self.message = message 221self.parent = parent 222if parent and parent.parent: 223self.root = parent.parent 224elif parent: 225self.root = parent 226else: 227self.root = None 228repo.last_post_id += 1 229 230def update_date(self): 231self.last_updated = datetime.now() 232with db.session.no_autoflush: 233if self.parent is not None: 234self.parent.update_date() 235 236 237class UserNotification(db.Model): 238id = db.Column(db.Integer, primary_key=True) 239user_username = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 240notification_id = db.Column(db.BigInteger, db.ForeignKey("notification.id")) 241attention_level = db.Column(db.SmallInteger, nullable=False) # 0 is read 242read_time = db.Column(db.DateTime, nullable=True) 243 244user = db.relationship("User", back_populates="notifications") 245notification = db.relationship("Notification", back_populates="notifications") 246 247__table_args__ = (db.UniqueConstraint("user_username", "notification_id", name="_user_notification_uc"),) 248 249def __init__(self, user, notification, level): 250self.user_username = user.username 251self.notification_id = notification.id 252self.attention_level = level 253 254def read(self): 255self.read_time = datetime.utcnow 256self.attention_level = 0 257 258 259class UserFollow(db.Model): 260id = db.Column(db.Integer, primary_key=True) 261follower_username = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False) 262followed_username = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False) 263 264follower = db.relationship("User", back_populates="followers", foreign_keys=[follower_username]) 265followed = db.relationship("User", back_populates="follows", foreign_keys=[followed_username]) 266 267def __init__(self, follower_username, followed_username): 268self.follower_username = follower_username 269self.followed_username = followed_username 270 271 272class Notification(db.Model): 273id = db.Column(db.BigInteger, primary_key=True, autoincrement=True) 274data = db.Column(db.dialects.postgresql.JSONB, nullable=False, default={}) 275notifications = db.relationship("UserNotification", back_populates="notification") 276timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now) 277 278def __init__(self, json): 279self.data = json 280 281 282class PullRequest(db.Model): 283id = db.Column(db.BigInteger, primary_key=True, autoincrement=True) 284head_route = db.Column(db.String(98), db.ForeignKey("repo.route", ondelete="CASCADE"), nullable=False) 285base_route = db.Column(db.String(98), db.ForeignKey("repo.route", ondelete="CASCADE"), nullable=False) 286owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 287state = db.Column(db.SmallInteger, nullable=False, default=0) # 0 pending, 1 merged, 2 rejected 288 289head = db.relationship("Repo", back_populates="heads", foreign_keys=[head_route]) 290base = db.relationship("Repo", back_populates="bases", foreign_keys=[base_route]) 291 292head_branch = db.Column(db.String(64), nullable=False) 293base_branch = db.Column(db.String(64), nullable=False) 294 295owner = db.relationship("User", back_populates="prs") 296timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now) 297 298def __init__(self, head, head_branch, base, base_branch, owner): 299self.head = head 300self.base = base 301self.head_branch = head_branch 302self.base_branch = base_branch 303self.owner = owner 304