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 222self.root = parent.parent if parent.parent else parent 223repo.last_post_id += 1 224 225def update_date(self): 226self.last_updated = datetime.now() 227with db.session.no_autoflush: 228if self.parent is not None: 229self.parent.update_date() 230 231 232class UserNotification(db.Model): 233id = db.Column(db.Integer, primary_key=True) 234user_username = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 235notification_id = db.Column(db.BigInteger, db.ForeignKey("notification.id")) 236attention_level = db.Column(db.SmallInteger, nullable=False) # 0 is read 237read_time = db.Column(db.DateTime, nullable=True) 238 239user = db.relationship("User", back_populates="notifications") 240notification = db.relationship("Notification", back_populates="notifications") 241 242__table_args__ = (db.UniqueConstraint("user_username", "notification_id", name="_user_notification_uc"),) 243 244def __init__(self, user, notification, level): 245self.user_username = user.username 246self.notification_id = notification.id 247self.attention_level = level 248 249def read(self): 250self.read_time = datetime.utcnow 251self.attention_level = 0 252 253 254class UserFollow(db.Model): 255id = db.Column(db.Integer, primary_key=True) 256follower_username = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False) 257followed_username = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False) 258 259follower = db.relationship("User", back_populates="followers", foreign_keys=[follower_username]) 260followed = db.relationship("User", back_populates="follows", foreign_keys=[followed_username]) 261 262def __init__(self, follower_username, followed_username): 263self.follower_username = follower_username 264self.followed_username = followed_username 265 266 267class Notification(db.Model): 268id = db.Column(db.BigInteger, primary_key=True, autoincrement=True) 269data = db.Column(db.dialects.postgresql.JSONB, nullable=False, default={}) 270notifications = db.relationship("UserNotification", back_populates="notification") 271timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now) 272 273def __init__(self, json): 274self.data = json 275 276 277class PullRequest(db.Model): 278id = db.Column(db.BigInteger, primary_key=True, autoincrement=True) 279head_route = db.Column(db.String(98), db.ForeignKey("repo.route", ondelete="CASCADE"), nullable=False) 280base_route = db.Column(db.String(98), db.ForeignKey("repo.route", ondelete="CASCADE"), nullable=False) 281owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 282state = db.Column(db.SmallInteger, nullable=False, default=0) # 0 pending, 1 merged, 2 rejected 283 284head = db.relationship("Repo", back_populates="heads", foreign_keys=[head_route]) 285base = db.relationship("Repo", back_populates="bases", foreign_keys=[base_route]) 286 287head_branch = db.Column(db.String(64), nullable=False) 288base_branch = db.Column(db.String(64), nullable=False) 289 290owner = db.relationship("User", back_populates="prs") 291timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now) 292 293def __init__(self, head, head_branch, base, base_branch, owner): 294self.head = head 295self.base = base 296self.head_branch = head_branch 297self.base_branch = base_branch 298self.owner = owner 299