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 • 16.07 kiB
Python script, ASCII text executable
        
            
1
__all__ = [
2
"RepoAccess",
3
"RepoFavourite",
4
"Repo",
5
"UserFollow",
6
"UserNotification",
7
"User",
8
"Notification",
9
"PostVote",
10
"Post",
11
"Commit",
12
"PullRequest",
13
]
14
15
import subprocess
16
from app import app, db, bcrypt
17
import git
18
from datetime import datetime
19
from enum import Enum
20
from PIL import Image
21
from cairosvg import svg2png
22
import os
23
import config
24
import cairosvg
25
import random
26
import celery_tasks
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
default_page_length = db.Column(db.SmallInteger, nullable=False, default=32, server_default="32")
96
max_post_nesting = db.Column(db.SmallInteger, nullable=False, default=3, server_default="3")
97
98
repositories = db.relationship("Repo", back_populates="owner")
99
followers = db.relationship("UserFollow", back_populates="followed", foreign_keys="[UserFollow.followed_username]")
100
follows = db.relationship("UserFollow", back_populates="follower", foreign_keys="[UserFollow.follower_username]")
101
repo_access = db.relationship("RepoAccess", back_populates="user")
102
votes = db.relationship("PostVote", back_populates="user")
103
favourites = db.relationship("RepoFavourite", back_populates="user")
104
105
commits = db.relationship("Commit", back_populates="owner")
106
posts = db.relationship("Post", back_populates="owner")
107
prs = db.relationship("PullRequest", back_populates="owner")
108
notifications = db.relationship("UserNotification", back_populates="user")
109
110
def __init__(self, username, password, email=None, display_name=None):
111
self.username = username
112
self.password_hashed = bcrypt.generate_password_hash(password, config.HASHING_ROUNDS).decode("utf-8")
113
self.email = email
114
self.display_name = display_name
115
116
# Create the user's directory
117
if not os.path.exists(os.path.join(config.REPOS_PATH, username)):
118
os.makedirs(os.path.join(config.REPOS_PATH, username))
119
if not os.path.exists(os.path.join(config.USERDATA_PATH, username)):
120
os.makedirs(os.path.join(config.USERDATA_PATH, username))
121
122
avatar_name = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH))
123
if os.path.join(config.DEFAULT_AVATARS_PATH, avatar_name).endswith(".svg"):
124
cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatar_name),
125
write_to="/tmp/roundabout-avatar.png")
126
avatar = Image.open("/tmp/roundabout-avatar.png")
127
else:
128
avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatar_name))
129
avatar.thumbnail(config.AVATAR_SIZE)
130
avatar.save(os.path.join(config.USERDATA_PATH, username, "avatar.png"))
131
132
# Create the configuration repo
133
config_repo = Repo(self, ".config", 0)
134
db.session.add(config_repo)
135
notification = Notification({"type": "welcome"})
136
db.session.add(notification)
137
db.session.commit()
138
139
user_notification = UserNotification(self, notification, 1)
140
db.session.add(user_notification)
141
db.session.flush()
142
celery_tasks.send_notification.apply_async(args=[user_notification.id])
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