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