By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 models.py

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