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