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.41 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 user in RepoFavourite.query.filter_by(repo_route=repo.route, notify_commit=True).all():
216
user_notification = UserNotification(user, notification, 1)
217
db.session.add(user_notification)
218
db.session.flush()
219
celery_tasks.send_notification.apply_async(args=[user_notification.id])
220
221
222
class Post(db.Model):
223
identifier = db.Column(db.String(109), unique=True, nullable=False, primary_key=True)
224
number = db.Column(db.Integer, nullable=False)
225
repo_name = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
226
owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
227
votes = db.relationship("PostVote", back_populates="post")
228
vote_sum = db.Column(db.Integer, nullable=False, default=0)
229
230
parent_id = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True)
231
root_id = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True)
232
state = db.Column(db.SmallInteger, nullable=True, default=1)
233
234
date = db.Column(db.DateTime, default=datetime.now)
235
last_updated = db.Column(db.DateTime, default=datetime.now)
236
subject = db.Column(db.Unicode(384))
237
message = db.Column(db.UnicodeText)
238
repo = db.relationship("Repo", back_populates="posts")
239
owner = db.relationship("User", back_populates="posts")
240
parent = db.relationship("Post", back_populates="children",
241
primaryjoin="Post.parent_id==Post.identifier",
242
foreign_keys="[Post.parent_id]", remote_side="Post.identifier")
243
root = db.relationship("Post",
244
primaryjoin="Post.root_id==Post.identifier",
245
foreign_keys="[Post.root_id]", remote_side="Post.identifier")
246
children = db.relationship("Post",
247
remote_side="Post.parent_id",
248
primaryjoin="Post.identifier==Post.parent_id",
249
foreign_keys="[Post.parent_id]")
250
251
def __init__(self, owner, repo, parent, subject, message):
252
self.identifier = f"{repo.route}/{repo.last_post_id}"
253
self.number = repo.last_post_id
254
self.repo_name = repo.route
255
self.repo = repo
256
self.owner_name = owner.username
257
self.owner = owner
258
self.subject = subject
259
self.message = message
260
self.parent = parent
261
if parent and parent.parent:
262
self.root = parent.parent
263
elif parent:
264
self.root = parent
265
else:
266
self.root = None
267
repo.last_post_id += 1
268
269
def update_date(self):
270
self.last_updated = datetime.now()
271
with db.session.no_autoflush:
272
if self.parent is not None:
273
self.parent.update_date()
274
275
276
class UserNotification(db.Model):
277
id = db.Column(db.Integer, primary_key=True)
278
user_username = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
279
notification_id = db.Column(db.BigInteger, db.ForeignKey("notification.id"))
280
attention_level = db.Column(db.SmallInteger, nullable=False) # 0 is read
281
read_time = db.Column(db.DateTime, nullable=True)
282
283
user = db.relationship("User", back_populates="notifications")
284
notification = db.relationship("Notification", back_populates="notifications")
285
286
__table_args__ = (db.UniqueConstraint("user_username", "notification_id", name="_user_notification_uc"),)
287
288
def __init__(self, user, notification, level):
289
self.user_username = user.username
290
self.notification_id = notification.id
291
self.attention_level = level
292
293
def mark_read(self):
294
self.read_time = datetime.utcnow()
295
self.attention_level = 0
296
297
def mark_unread(self):
298
self.attention_level = 4
299
300
301
class UserFollow(db.Model):
302
id = db.Column(db.Integer, primary_key=True)
303
follower_username = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False)
304
followed_username = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False)
305
306
follower = db.relationship("User", back_populates="followers", foreign_keys=[follower_username])
307
followed = db.relationship("User", back_populates="follows", foreign_keys=[followed_username])
308
309
def __init__(self, follower_username, followed_username):
310
self.follower_username = follower_username
311
self.followed_username = followed_username
312
313
314
class Notification(db.Model):
315
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
316
data = db.Column(db.dialects.postgresql.JSONB, nullable=False, default={})
317
notifications = db.relationship("UserNotification", back_populates="notification")
318
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now)
319
320
def __init__(self, json):
321
self.data = json
322
323
324
class PullRequest(db.Model):
325
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
326
head_route = db.Column(db.String(98), db.ForeignKey("repo.route", ondelete="CASCADE"), nullable=False)
327
base_route = db.Column(db.String(98), db.ForeignKey("repo.route", ondelete="CASCADE"), nullable=False)
328
owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
329
state = db.Column(db.SmallInteger, nullable=False, default=0) # 0 pending, 1 merged, 2 rejected
330
331
head = db.relationship("Repo", back_populates="heads", foreign_keys=[head_route])
332
base = db.relationship("Repo", back_populates="bases", foreign_keys=[base_route])
333
334
head_branch = db.Column(db.String(64), nullable=False)
335
base_branch = db.Column(db.String(64), nullable=False)
336
337
owner = db.relationship("User", back_populates="prs")
338
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now)
339
340
def __init__(self, head, head_branch, base, base_branch, owner):
341
self.head = head
342
self.base = base
343
self.head_branch = head_branch
344
self.base_branch = base_branch
345
self.owner = owner
346