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) 29userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 30repoRoute = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 31accessLevel = db.Column(db.SmallInteger(), nullable=False) # 0 read-only, 1 read-write, 2 admin 32 33user = db.relationship("User", back_populates="repoAccess") 34repo = db.relationship("Repo", back_populates="repoAccess") 35 36__table_args__ = (db.UniqueConstraint("userUsername", "repoRoute", name="_user_repo_uc"),) 37 38def __init__(self, user, repo, level): 39self.userUsername = user.username 40self.repoRoute = repo.route 41self.accessLevel = level 42 43 44class RepoFavourite(db.Model): 45id = db.Column(db.Integer, primary_key=True) 46userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 47repoRoute = 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("userUsername", "repoRoute", name="_user_repo_uc1"),) 53 54def __init__(self, user, repo): 55self.userUsername = user.username 56self.repoRoute = repo.route 57 58 59class PostVote(db.Model): 60id = db.Column(db.Integer, primary_key=True) 61userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 62postIdentifier = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=False) 63voteScore = 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("userUsername", "postIdentifier", name="_user_post_uc"),) 69 70def __init__(self, user, post, score): 71self.userUsername = user.username 72self.postIdentifier = post.identifier 73self.voteScore = score 74 75 76class User(db.Model): 77username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True) 78displayName = db.Column(db.Unicode(128), unique=False, nullable=True) 79bio = db.Column(db.Unicode(512), unique=False, nullable=True) 80passwordHashed = db.Column(db.String(60), nullable=False) 81email = db.Column(db.String(254), nullable=True) 82company = db.Column(db.Unicode(64), nullable=True) 83companyURL = db.Column(db.String(256), nullable=True) 84URL = db.Column(db.String(256), nullable=True) 85showMail = db.Column(db.Boolean, default=False, nullable=False) 86location = db.Column(db.Unicode(64), nullable=True) 87creationDate = 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.followedUsername]") 91follows = db.relationship("UserFollow", back_populates="follower", foreign_keys="[UserFollow.followerUsername]") 92repoAccess = 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, displayName=None): 101self.username = username 102self.passwordHashed = bcrypt.generate_password_hash(password, config.HASHING_ROUNDS).decode("utf-8") 103self.email = email 104self.displayName = displayName 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 112avatarName = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH)) 113if os.path.join(config.DEFAULT_AVATARS_PATH, avatarName).endswith(".svg"): 114cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatarName), 115write_to="/tmp/roundabout-avatar.png") 116avatar = Image.open("/tmp/roundabout-avatar.png") 117else: 118avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatarName)) 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) 125ownerName = 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) 131creationDate = db.Column(db.DateTime, default=datetime.utcnow) 132 133defaultBranch = db.Column(db.String(64), nullable=True, default="") 134 135commits = db.relationship("Commit", back_populates="repo") 136posts = db.relationship("Post", back_populates="repo") 137repoAccess = db.relationship("RepoAccess", back_populates="repo") 138favourites = db.relationship("RepoFavourite", back_populates="repo") 139 140lastPostID = 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.ownerName = owner.username 146self.owner = owner 147self.visibility = visibility 148 149# Add the owner as an admin 150repoAccess = RepoAccess(owner, self, 2) 151db.session.add(repoAccess) 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) 157repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 158ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 159ownerIdentity = db.Column(db.String(321)) 160receiveDate = db.Column(db.DateTime, default=datetime.now) 161authorDate = 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, ownerIdentity): 167self.identifier = f"{repo.route}/{sha}" 168self.sha = sha 169self.repoName = repo.route 170self.repo = repo 171self.ownerName = owner.username 172self.owner = owner 173self.authorDate = datetime.fromtimestamp(int(date)) 174self.message = message 175self.ownerIdentity = ownerIdentity 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) 181repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 182ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 183votes = db.relationship("PostVote", back_populates="post") 184voteSum = db.Column(db.Integer, nullable=False, default=0) 185 186parentID = 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) 190lastUpdated = 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.parentID") 197 198def __init__(self, owner, repo, parent, subject, message): 199self.identifier = f"{repo.route}/{repo.lastPostID}" 200self.number = repo.lastPostID 201self.repoName = repo.route 202self.repo = repo 203self.ownerName = owner.username 204self.owner = owner 205self.subject = subject 206self.message = message 207self.parent = parent 208repo.lastPostID += 1 209 210def updateDate(self): 211self.lastUpdated = datetime.now() 212with db.session.no_autoflush: 213if self.parent is not None: 214self.parent.updateDate() 215 216 217class UserNotification(db.Model): 218id = db.Column(db.Integer, primary_key=True) 219userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 220notificationID = db.Column(db.BigInteger, db.ForeignKey("notification.id")) 221attentionLevel = db.Column(db.SmallInteger, nullable=False) # 0 is read 222readTime = 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("userUsername", "notificationID", name="_user_notification_uc"),) 228 229def __init__(self, user, notification, level): 230self.userUsername = user.username 231self.notificationID = notification.id 232self.attentionLevel = level 233 234def read(self): 235self.readTime = datetime.utcnow 236self.attentionLevel = 0 237 238 239class UserFollow(db.Model): 240id = db.Column(db.Integer, primary_key=True) 241followerUsername = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False) 242followedUsername = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False) 243 244follower = db.relationship("User", back_populates="follows", foreign_keys=[followedUsername]) 245followed = db.relationship("User", back_populates="followers", foreign_keys=[followerUsername]) 246 247def __init__(self, followerUsername, followedUsername): 248self.followerUsername = followerUsername 249self.followedUsername = followedUsername 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 261# def sendTo(self, users, level): 262# for user in users: 263# db.session.add(UserNotification(user, self, level)) 264 265