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