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.86 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
class Repo(db.Model):
145
route = db.Column(db.String(98), unique=True, nullable=False, primary_key=True)
146
owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
147
name = db.Column(db.String(64), nullable=False)
148
owner = db.relationship("User", back_populates="repositories")
149
visibility = db.Column(db.SmallInteger(), nullable=False)
150
info = db.Column(db.Unicode(512), nullable=True)
151
url = db.Column(db.String(256), nullable=True)
152
creation_date = db.Column(db.DateTime, default=datetime.utcnow)
153
154
default_branch = db.Column(db.String(64), nullable=True, default="")
155
156
commits = db.relationship("Commit", back_populates="repo")
157
posts = db.relationship("Post", back_populates="repo")
158
repo_access = db.relationship("RepoAccess", back_populates="repo")
159
favourites = db.relationship("RepoFavourite", back_populates="repo")
160
heads = db.relationship("PullRequest", back_populates="head", foreign_keys="[PullRequest.head_route]")
161
bases = db.relationship("PullRequest", back_populates="base", foreign_keys="[PullRequest.base_route]")
162
163
last_post_id = db.Column(db.Integer, nullable=False, default=0)
164
165
def __init__(self, owner, name, visibility):
166
self.route = f"/{owner.username}/{name}"
167
self.name = name
168
self.owner_name = owner.username
169
self.owner = owner
170
self.visibility = visibility
171
172
# Add the owner as an admin
173
repo_access = RepoAccess(owner, self, 2)
174
db.session.add(repo_access)
175
176
# Create the directory
177
if not os.path.exists(os.path.join(config.REPOS_PATH, self.owner_name, self.name)):
178
subprocess.run(["git", "init", self.name],
179
cwd=os.path.join(config.REPOS_PATH, self.owner_name))
180
181
182
class Commit(db.Model):
183
identifier = db.Column(db.String(227), unique=True, nullable=False, primary_key=True)
184
sha = db.Column(db.String(128), nullable=False)
185
repo_name = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
186
owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
187
owner_identity = db.Column(db.String(321))
188
receive_date = db.Column(db.DateTime, default=datetime.now)
189
author_date = db.Column(db.DateTime)
190
message = db.Column(db.UnicodeText)
191
repo = db.relationship("Repo", back_populates="commits")
192
owner = db.relationship("User", back_populates="commits")
193
194
def __init__(self, sha, owner, repo, date, message, owner_identity):
195
self.identifier = f"{repo.route}/{sha}"
196
self.sha = sha
197
self.repo_name = repo.route
198
self.repo = repo
199
self.owner_name = owner.username
200
self.owner = owner
201
self.author_date = datetime.fromtimestamp(int(date))
202
self.message = message
203
self.owner_identity = owner_identity
204
205
notification = Notification({"type": "commit", "repo": repo.route, "commit": sha})
206
db.session.add(notification)
207
db.session.flush() # save the notification to get the ID
208
209
# Send a notification to all users who have enabled commit notifications for this repo
210
for user in RepoFavourite.query.filter_by(repo_route=repo.route, notify_commit=True).all():
211
user_notification = UserNotification(user, notification, 1)
212
db.session.add(user_notification)
213
db.session.flush()
214
celery_tasks.send_notification.apply_async(args=[user_notification.id])
215
216
217
class Post(db.Model):
218
identifier = db.Column(db.String(109), unique=True, nullable=False, primary_key=True)
219
number = db.Column(db.Integer, nullable=False)
220
repo_name = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
221
owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
222
votes = db.relationship("PostVote", back_populates="post")
223
vote_sum = db.Column(db.Integer, nullable=False, default=0)
224
225
parent_id = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True)
226
root_id = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True)
227
state = db.Column(db.SmallInteger, nullable=True, default=1)
228
229
date = db.Column(db.DateTime, default=datetime.now)
230
last_updated = db.Column(db.DateTime, default=datetime.now)
231
subject = db.Column(db.Unicode(384))
232
message = db.Column(db.UnicodeText)
233
repo = db.relationship("Repo", back_populates="posts")
234
owner = db.relationship("User", back_populates="posts")
235
parent = db.relationship("Post", back_populates="children",
236
primaryjoin="Post.parent_id==Post.identifier",
237
foreign_keys="[Post.parent_id]", remote_side="Post.identifier")
238
root = db.relationship("Post",
239
primaryjoin="Post.root_id==Post.identifier",
240
foreign_keys="[Post.root_id]", remote_side="Post.identifier")
241
children = db.relationship("Post",
242
remote_side="Post.parent_id",
243
primaryjoin="Post.identifier==Post.parent_id",
244
foreign_keys="[Post.parent_id]")
245
246
def __init__(self, owner, repo, parent, subject, message):
247
self.identifier = f"{repo.route}/{repo.last_post_id}"
248
self.number = repo.last_post_id
249
self.repo_name = repo.route
250
self.repo = repo
251
self.owner_name = owner.username
252
self.owner = owner
253
self.subject = subject
254
self.message = message
255
self.parent = parent
256
if parent and parent.parent:
257
self.root = parent.parent
258
elif parent:
259
self.root = parent
260
else:
261
self.root = None
262
repo.last_post_id += 1
263
264
def update_date(self):
265
self.last_updated = datetime.now()
266
with db.session.no_autoflush:
267
if self.parent is not None:
268
self.parent.update_date()
269
270
271
class UserNotification(db.Model):
272
id = db.Column(db.Integer, primary_key=True)
273
user_username = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
274
notification_id = db.Column(db.BigInteger, db.ForeignKey("notification.id"))
275
attention_level = db.Column(db.SmallInteger, nullable=False) # 0 is read
276
read_time = db.Column(db.DateTime, nullable=True)
277
278
user = db.relationship("User", back_populates="notifications")
279
notification = db.relationship("Notification", back_populates="notifications")
280
281
__table_args__ = (db.UniqueConstraint("user_username", "notification_id", name="_user_notification_uc"),)
282
283
def __init__(self, user, notification, level):
284
self.user_username = user.username
285
self.notification_id = notification.id
286
self.attention_level = level
287
288
def mark_read(self):
289
self.read_time = datetime.utcnow()
290
self.attention_level = 0
291
292
def mark_unread(self):
293
self.attention_level = 4
294
295
296
class UserFollow(db.Model):
297
id = db.Column(db.Integer, primary_key=True)
298
follower_username = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False)
299
followed_username = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False)
300
301
follower = db.relationship("User", back_populates="followers", foreign_keys=[follower_username])
302
followed = db.relationship("User", back_populates="follows", foreign_keys=[followed_username])
303
304
def __init__(self, follower_username, followed_username):
305
self.follower_username = follower_username
306
self.followed_username = followed_username
307
308
309
class Notification(db.Model):
310
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
311
data = db.Column(db.dialects.postgresql.JSONB, nullable=False, default={})
312
notifications = db.relationship("UserNotification", back_populates="notification")
313
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now)
314
315
def __init__(self, json):
316
self.data = json
317
318
319
class PullRequest(db.Model):
320
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
321
head_route = db.Column(db.String(98), db.ForeignKey("repo.route", ondelete="CASCADE"), nullable=False)
322
base_route = db.Column(db.String(98), db.ForeignKey("repo.route", ondelete="CASCADE"), nullable=False)
323
owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
324
state = db.Column(db.SmallInteger, nullable=False, default=0) # 0 pending, 1 merged, 2 rejected
325
326
head = db.relationship("Repo", back_populates="heads", foreign_keys=[head_route])
327
base = db.relationship("Repo", back_populates="bases", foreign_keys=[base_route])
328
329
head_branch = db.Column(db.String(64), nullable=False)
330
base_branch = db.Column(db.String(64), nullable=False)
331
332
owner = db.relationship("User", back_populates="prs")
333
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now)
334
335
def __init__(self, head, head_branch, base, base_branch, owner):
336
self.head = head
337
self.base = base
338
self.head_branch = head_branch
339
self.base_branch = base_branch
340
self.owner = owner
341