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