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