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 7 8with app.app_context(): 9class RepoAccess(db.Model): 10id = db.Column(db.Integer, primary_key=True) 11userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 12repoRoute = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 13accessLevel = db.Column(db.SmallInteger(), nullable=False) # 0 read-only, 1 read-write, 2 admin 14 15user = db.relationship("User", back_populates="repoAccess") 16repo = db.relationship("Repo", back_populates="repoAccess") 17 18__table_args__ = (db.UniqueConstraint("userUsername", "repoRoute", name="_user_repo_uc"),) 19 20def __init__(self, user, repo, level): 21self.userUsername = user.username 22self.repoRoute = repo.route 23self.accessLevel = level 24 25 26class RepoFavourite(db.Model): 27id = db.Column(db.Integer, primary_key=True) 28userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 29repoRoute = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 30 31user = db.relationship("User", back_populates="favourites") 32repo = db.relationship("Repo", back_populates="favourites") 33 34__table_args__ = (db.UniqueConstraint("userUsername", "repoRoute", name="_user_repo_uc1"),) 35 36def __init__(self, user, repo): 37self.userUsername = user.username 38self.repoRoute = repo.route 39 40 41class PostVote(db.Model): 42id = db.Column(db.Integer, primary_key=True) 43userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 44postIdentifier = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=False) 45voteScore = db.Column(db.SmallInteger(), nullable=False) 46 47user = db.relationship("User", back_populates="votes") 48post = db.relationship("Post", back_populates="votes") 49 50__table_args__ = (db.UniqueConstraint("userUsername", "postIdentifier", name="_user_post_uc"),) 51 52def __init__(self, user, post, score): 53self.userUsername = user.username 54self.postIdentifier = post.identifier 55self.voteScore = score 56 57 58class User(db.Model): 59username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True) 60displayName = db.Column(db.Unicode(128), unique=False, nullable=True) 61bio = db.Column(db.Unicode(512), unique=False, nullable=True) 62passwordHashed = db.Column(db.String(60), nullable=False) 63email = db.Column(db.String(254), nullable=True) 64company = db.Column(db.Unicode(64), nullable=True) 65companyURL = db.Column(db.String(256), nullable=True) 66URL = db.Column(db.String(256), nullable=True) 67showMail = db.Column(db.Boolean, default=False, nullable=False) 68location = db.Column(db.Unicode(64), nullable=True) 69creationDate = db.Column(db.DateTime, default=datetime.utcnow) 70 71repositories = db.relationship("Repo", back_populates="owner") 72followers = db.relationship("UserFollow", back_populates="followed") 73follows = db.relationship("UserFollow", back_populates="follower") 74repoAccess = db.relationship("RepoAccess", back_populates="user") 75votes = db.relationship("PostVote", back_populates="user") 76generatedNotifications = db.relationship("Notification", back_populates="author") 77favourites = db.relationship("RepoFavourite", back_populates="user") 78 79commits = db.relationship("Commit", back_populates="owner") 80posts = db.relationship("Post", back_populates="owner") 81notifications = db.relationship("UserNotification", back_populates="user") 82 83def __init__(self, username, password, email=None, displayName=None): 84self.username = username 85self.passwordHashed = bcrypt.generate_password_hash(password, config.HASHING_ROUNDS).decode("utf-8") 86self.email = email 87self.displayName = displayName 88 89# Create the user's directory 90if not os.path.exists(os.path.join(config.REPOS_PATH, username)): 91os.makedirs(os.path.join(config.REPOS_PATH, username)) 92if not os.path.exists(os.path.join(config.USERDATA_PATH, username)): 93os.makedirs(os.path.join(config.USERDATA_PATH, username)) 94 95avatarName = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH)) 96if os.path.join(config.DEFAULT_AVATARS_PATH, avatarName).endswith(".svg"): 97cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatarName), 98write_to="/tmp/roundabout-avatar.png") 99avatar = Image.open("/tmp/roundabout-avatar.png") 100else: 101avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatarName)) 102avatar.thumbnail(config.AVATAR_SIZE) 103avatar.save(os.path.join(config.USERDATA_PATH, username, "avatar.png")) 104 105 106class Repo(db.Model): 107route = db.Column(db.String(98), unique=True, nullable=False, primary_key=True) 108ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 109name = db.Column(db.String(64), nullable=False) 110owner = db.relationship("User", back_populates="repositories") 111visibility = db.Column(db.SmallInteger(), nullable=False) 112info = db.Column(db.Unicode(512), nullable=True) 113URL = db.Column(db.String(256), nullable=True) 114creationDate = db.Column(db.DateTime, default=datetime.utcnow) 115 116defaultBranch = db.Column(db.String(64), nullable=True, default="") 117 118commits = db.relationship("Commit", back_populates="repo") 119posts = db.relationship("Post", back_populates="repo") 120repoAccess = db.relationship("RepoAccess", back_populates="repo") 121favourites = db.relationship("RepoFavourite", back_populates="repo") 122 123lastPostID = db.Column(db.Integer, nullable=False, default=0) 124 125def __init__(self, owner, name, visibility): 126self.route = f"/{owner.username}/{name}" 127self.name = name 128self.ownerName = owner.username 129self.owner = owner 130self.visibility = visibility 131 132# Add the owner as an admin 133repoAccess = RepoAccess(owner, self, 2) 134db.session.add(repoAccess) 135 136 137class Commit(db.Model): 138identifier = db.Column(db.String(227), unique=True, nullable=False, primary_key=True) 139sha = db.Column(db.String(128), nullable=False) 140repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 141ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 142ownerIdentity = db.Column(db.String(321)) 143receiveDate = db.Column(db.DateTime, default=datetime.now) 144authorDate = db.Column(db.DateTime) 145message = db.Column(db.UnicodeText) 146repo = db.relationship("Repo", back_populates="commits") 147owner = db.relationship("User", back_populates="commits") 148 149def __init__(self, sha, owner, repo, date, message, ownerIdentity): 150self.identifier = f"{repo.route}/{sha}" 151self.sha = sha 152self.repoName = repo.route 153self.repo = repo 154self.ownerName = owner.username 155self.owner = owner 156self.authorDate = datetime.fromtimestamp(int(date)) 157self.message = message 158self.ownerIdentity = ownerIdentity 159 160 161class Post(db.Model): 162identifier = db.Column(db.String(109), unique=True, nullable=False, primary_key=True) 163number = db.Column(db.Integer, nullable=False) 164repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 165ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 166votes = db.relationship("PostVote", back_populates="post") 167voteSum = db.Column(db.Integer, nullable=False, default=0) 168 169parentID = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True) 170state = db.Column(db.SmallInteger, nullable=True, default=1) 171 172date = db.Column(db.DateTime, default=datetime.now) 173lastUpdated = db.Column(db.DateTime, default=datetime.now) 174subject = db.Column(db.Unicode(384)) 175message = db.Column(db.UnicodeText) 176repo = db.relationship("Repo", back_populates="posts") 177owner = db.relationship("User", back_populates="posts") 178parent = db.relationship("Post", back_populates="children", remote_side="Post.identifier") 179children = db.relationship("Post", back_populates="parent", remote_side="Post.parentID") 180 181def __init__(self, owner, repo, parent, subject, message): 182self.identifier = f"{repo.route}/{repo.lastPostID}" 183self.number = repo.lastPostID 184self.repoName = repo.route 185self.repo = repo 186self.ownerName = owner.username 187self.owner = owner 188self.subject = subject 189self.message = message 190self.parent = parent 191repo.lastPostID += 1 192 193def updateDate(self): 194self.lastUpdated = datetime.now() 195with db.session.no_autoflush: 196if self.parent is not None: 197self.parent.updateDate() 198 199 200class UserNotification(db.Model): 201id = db.Column(db.Integer, primary_key=True) 202userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 203notificationID = db.Column(db.BigInteger, db.ForeignKey("notification.id")) 204attentionLevel = db.Column(db.SmallInteger, nullable=False) # 0 is read 205readTime = db.Column(db.DateTime, nullable=True) 206 207user = db.relationship("User", back_populates="notifications") 208notification = db.relationship("Notification", back_populates="notifications") 209 210__table_args__ = (db.UniqueConstraint("userUsername", "notificationID", name="_user_notification_uc"),) 211 212def __init__(self, user, notification, level): 213self.userUsername = user.username 214self.notificationID = notification.id 215self.attentionLevel = level 216 217def read(self): 218self.readTime = datetime.utcnow 219self.attentionLevel = 0 220 221 222class UserFollow(db.Model): 223id = db.Column(db.Integer, primary_key=True) 224followerUsername = db.Column(db.String(32), db.ForeignKey("follower.username"), nullable=False) 225followedUsername = db.Column(db.String(32), db.ForeignKey("followed.username"), nullable=False) 226 227follower = db.relationship("User", back_populates="follows") 228followed = db.relationship("User", back_populates="followers") 229 230def __init__(self, user, notification, level): 231self.userUsername = user.username 232self.notificationID = notification.id 233self.attentionLevel = level 234 235def read(self): 236self.readTime = datetime.utcnow 237self.attentionLevel = 0 238 239 240class Notification(db.Model): 241id = db.Column(db.BigInteger, primary_key=True, autoincrement=True) 242data = db.Column(db.dialects.postgresql.JSONB, nullable=False, default={}) 243notifications = db.relationship("UserNotification", back_populates="notification") 244timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now) 245 246def __init__(self, json): 247self.data = json 248 249def sendTo(self, users, level): 250for user in users: 251db.session.add(UserNotification(user, self, level)) 252 253