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

 app.py

View raw Download
text/x-script.python • 36.5 kiB
Python script, ASCII text executable
        
            
1
import os
2
import random
3
import subprocess
4
from functools import wraps
5
6
import cairosvg
7
import flask
8
from flask_sqlalchemy import SQLAlchemy
9
import git
10
import mimetypes
11
import magic
12
from flask_bcrypt import Bcrypt
13
from markupsafe import escape, Markup
14
from flask_migrate import Migrate
15
from datetime import datetime
16
from enum import Enum
17
import shutil
18
from PIL import Image
19
from cairosvg import svg2png
20
import platform
21
22
import config
23
24
app = flask.Flask(__name__)
25
26
from flask_httpauth import HTTPBasicAuth
27
28
auth = HTTPBasicAuth()
29
30
app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI
31
app.config["SECRET_KEY"] = config.DB_PASSWORD
32
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
33
db = SQLAlchemy(app)
34
bcrypt = Bcrypt(app)
35
migrate = Migrate(app, db)
36
37
38
def gitCommand(repo, data, *args):
39
if not os.path.isdir(repo):
40
raise FileNotFoundError("Repo not found")
41
env = os.environ.copy()
42
43
command = ["git", *args]
44
45
proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE,
46
stdin=subprocess.PIPE)
47
print(command)
48
49
if data:
50
proc.stdin.write(data)
51
52
out, err = proc.communicate()
53
return out
54
55
56
def onlyChars(string, chars):
57
for i in string:
58
if i not in chars:
59
return False
60
return True
61
62
63
with app.app_context():
64
class RepoAccess(db.Model):
65
id = db.Column(db.Integer, primary_key=True)
66
userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
67
repoRoute = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
68
accessLevel = db.Column(db.SmallInteger(), nullable=False) # 0 read-only, 1 read-write, 2 admin
69
70
user = db.relationship("User", back_populates="repoAccess")
71
repo = db.relationship("Repo", back_populates="repoAccess")
72
73
__table_args__ = (db.UniqueConstraint("userUsername", "repoRoute", name="_user_repo_uc"),)
74
75
def __init__(self, user, repo, level):
76
self.userUsername = user.username
77
self.repoRoute = repo.route
78
self.accessLevel = level
79
80
81
class PostVote(db.Model):
82
id = db.Column(db.Integer, primary_key=True)
83
userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
84
postIdentifier = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=False)
85
voteScore = db.Column(db.SmallInteger(), nullable=False)
86
87
user = db.relationship("User", back_populates="votes")
88
post = db.relationship("Post", back_populates="votes")
89
90
__table_args__ = (db.UniqueConstraint("userUsername", "postIdentifier", name="_user_post_uc"),)
91
92
def __init__(self, user, post, score):
93
self.userUsername = user.username
94
self.postIdentifier = post.identifier
95
self.voteScore = score
96
97
98
class User(db.Model):
99
username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True)
100
displayName = db.Column(db.Unicode(128), unique=False, nullable=True)
101
bio = db.Column(db.Unicode(512), unique=False, nullable=True)
102
passwordHashed = db.Column(db.String(60), nullable=False)
103
email = db.Column(db.String(254), nullable=True)
104
company = db.Column(db.Unicode(64), nullable=True)
105
companyURL = db.Column(db.String(256), nullable=True)
106
URL = db.Column(db.String(256), nullable=True)
107
showMail = db.Column(db.Boolean, default=False, nullable=False)
108
location = db.Column(db.Unicode(64), nullable=True)
109
creationDate = db.Column(db.DateTime, default=datetime.utcnow)
110
111
repositories = db.relationship("Repo", back_populates="owner")
112
repoAccess = db.relationship("RepoAccess", back_populates="user")
113
votes = db.relationship("PostVote", back_populates="user")
114
115
commits = db.relationship("Commit", back_populates="owner")
116
posts = db.relationship("Post", back_populates="owner")
117
118
def __init__(self, username, password, email=None, displayName=None):
119
self.username = username
120
self.passwordHashed = bcrypt.generate_password_hash(password, config.HASHING_ROUNDS).decode("utf-8")
121
self.email = email
122
self.displayName = displayName
123
124
# Create the user's directory
125
if not os.path.exists(os.path.join(config.REPOS_PATH, username)):
126
os.makedirs(os.path.join(config.REPOS_PATH, username))
127
if not os.path.exists(os.path.join(config.USERDATA_PATH, username)):
128
os.makedirs(os.path.join(config.USERDATA_PATH, username))
129
130
avatarName = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH))
131
if os.path.join(config.DEFAULT_AVATARS_PATH, avatarName).endswith(".svg"):
132
cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatarName),
133
write_to="/tmp/roundabout-avatar.png")
134
avatar = Image.open("/tmp/roundabout-avatar.png")
135
else:
136
avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatarName))
137
avatar.thumbnail(config.AVATAR_SIZE)
138
avatar.save(os.path.join(config.USERDATA_PATH, username, "avatar.png"))
139
140
141
class Repo(db.Model):
142
route = db.Column(db.String(98), unique=True, nullable=False, primary_key=True)
143
ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
144
name = db.Column(db.String(64), nullable=False)
145
owner = db.relationship("User", back_populates="repositories")
146
visibility = db.Column(db.SmallInteger(), nullable=False)
147
info = db.Column(db.Unicode(512), nullable=True)
148
URL = db.Column(db.String(256), nullable=True)
149
creationDate = db.Column(db.DateTime, default=datetime.utcnow)
150
151
defaultBranch = db.Column(db.String(64), nullable=True, default="")
152
153
commits = db.relationship("Commit", back_populates="repo")
154
posts = db.relationship("Post", back_populates="repo")
155
repoAccess = db.relationship("RepoAccess", back_populates="repo")
156
157
lastPostID = db.Column(db.Integer, nullable=False, default=0)
158
159
def __init__(self, owner, name, visibility):
160
self.route = f"/{owner.username}/{name}"
161
self.name = name
162
self.ownerName = owner.username
163
self.owner = owner
164
self.visibility = visibility
165
166
# Add the owner as an admin
167
repoAccess = RepoAccess(owner, self, 2)
168
db.session.add(repoAccess)
169
170
171
class Commit(db.Model):
172
identifier = db.Column(db.String(227), unique=True, nullable=False, primary_key=True)
173
sha = db.Column(db.String(128), nullable=False)
174
repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
175
ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
176
ownerIdentity = db.Column(db.String(321))
177
receiveDate = db.Column(db.DateTime, default=datetime.now)
178
authorDate = db.Column(db.DateTime)
179
message = db.Column(db.UnicodeText)
180
repo = db.relationship("Repo", back_populates="commits")
181
owner = db.relationship("User", back_populates="commits")
182
183
def __init__(self, sha, owner, repo, date, message, ownerIdentity):
184
self.identifier = f"{repo.route}/{sha}"
185
self.sha = sha
186
self.repoName = repo.route
187
self.repo = repo
188
self.ownerName = owner.username
189
self.owner = owner
190
self.authorDate = datetime.fromtimestamp(int(date))
191
self.message = message
192
self.ownerIdentity = ownerIdentity
193
194
195
class Post(db.Model):
196
identifier = db.Column(db.String(109), unique=True, nullable=False, primary_key=True)
197
number = db.Column(db.Integer, nullable=False)
198
repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
199
ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
200
votes = db.relationship("PostVote", back_populates="post")
201
voteSum = db.Column(db.Integer, nullable=False, default=0)
202
203
parentID = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True)
204
state = db.Column(db.SmallInteger, nullable=True, default=1)
205
206
date = db.Column(db.DateTime, default=datetime.now)
207
lastUpdated = db.Column(db.DateTime, default=datetime.now)
208
subject = db.Column(db.Unicode(384))
209
message = db.Column(db.UnicodeText)
210
repo = db.relationship("Repo", back_populates="posts")
211
owner = db.relationship("User", back_populates="posts")
212
parent = db.relationship("Post", back_populates="children", remote_side="Post.identifier")
213
children = db.relationship("Post", back_populates="parent", remote_side="Post.parentID")
214
215
def __init__(self, owner, repo, parent, subject, message):
216
self.identifier = f"{repo.route}/{repo.lastPostID}"
217
self.number = repo.lastPostID
218
self.repoName = repo.route
219
self.repo = repo
220
self.ownerName = owner.username
221
self.owner = owner
222
self.subject = subject
223
self.message = message
224
self.parent = parent
225
repo.lastPostID += 1
226
227
def updateDate(self):
228
self.lastUpdated = datetime.now()
229
with db.session.no_autoflush:
230
if self.parent is not None:
231
self.parent.updateDate()
232
233
234
def getPermissionLevel(loggedIn, username, repository):
235
user = User.query.filter_by(username=loggedIn).first()
236
repo = Repo.query.filter_by(route=f"/{username}/{repository}").first()
237
238
if user and repo:
239
permission = RepoAccess.query.filter_by(user=user, repo=repo).first()
240
if permission:
241
return permission.accessLevel
242
243
return None
244
245
246
def getVisibility(username, repository):
247
repo = Repo.query.filter_by(route=f"/{username}/{repository}").first()
248
249
if repo:
250
return repo.visibility
251
252
return None
253
254
255
import gitHTTP
256
import jinjaUtils
257
258
259
def humanSize(value, decimals=2, scale=1024,
260
units=("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")):
261
for unit in units:
262
if value < scale:
263
break
264
value /= scale
265
if int(value) == value:
266
# do not return decimals, if the value is already round
267
return int(value), unit
268
return round(value * 10 ** decimals) / 10 ** decimals, unit
269
270
271
def guessMIME(path):
272
if os.path.isdir(path):
273
mimetype = "inode/directory"
274
elif magic.from_file(path, mime=True):
275
mimetype = magic.from_file(path, mime=True)
276
else:
277
mimetype = "application/octet-stream"
278
return mimetype
279
280
281
def convertToHTML(path):
282
with open(path, "r") as f:
283
contents = f.read()
284
return contents
285
286
287
@app.context_processor
288
def default():
289
username = flask.session.get("username")
290
291
return {"loggedInUser": username}
292
293
294
@app.route("/")
295
def main():
296
return flask.render_template("home.html")
297
298
299
@app.route("/about/")
300
def about():
301
return flask.render_template("about.html", platform=platform)
302
303
304
@app.route("/settings/", methods=["GET", "POST"])
305
def settings():
306
if flask.request.method == "GET":
307
if not flask.session.get("username"):
308
flask.abort(401)
309
user = User.query.filter_by(username=flask.session.get("username")).first()
310
311
return flask.render_template("user-settings.html", user=user)
312
else:
313
user = User.query.filter_by(username=flask.session.get("username")).first()
314
315
user.displayName = flask.request.form["displayname"]
316
user.URL = flask.request.form["url"]
317
user.company = flask.request.form["company"]
318
user.companyURL = flask.request.form["companyurl"]
319
user.location = flask.request.form["location"]
320
user.showMail = flask.request.form.get("showmail", user.showMail)
321
322
db.session.commit()
323
324
flask.flash(Markup("<iconify-icon icon='mdi:check'></iconify-icon>Settings saved"), category="success")
325
return flask.redirect(f"/{flask.session.get('username')}", code=303)
326
327
328
@app.route("/accounts/", methods=["GET", "POST"])
329
def login():
330
if flask.request.method == "GET":
331
return flask.render_template("login.html")
332
else:
333
if "login" in flask.request.form:
334
username = flask.request.form["username"]
335
password = flask.request.form["password"]
336
337
user = User.query.filter_by(username=username).first()
338
339
if user and bcrypt.check_password_hash(user.passwordHashed, password):
340
flask.session["username"] = user.username
341
flask.flash(
342
Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged in as {username}"),
343
category="success")
344
return flask.redirect("/", code=303)
345
elif not user:
346
flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>User not found"),
347
category="alert")
348
return flask.render_template("login.html")
349
else:
350
flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>Invalid password"),
351
category="error")
352
return flask.render_template("login.html")
353
if "signup" in flask.request.form:
354
username = flask.request.form["username"]
355
password = flask.request.form["password"]
356
password2 = flask.request.form["password2"]
357
email = flask.request.form.get("email")
358
email2 = flask.request.form.get("email2") # repeat email is a honeypot
359
name = flask.request.form.get("name")
360
361
if not onlyChars(username, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
362
flask.flash(Markup(
363
"<iconify-icon icon='mdi:account-error'></iconify-icon>Usernames may only contain Latin alphabet, numbers, '-' and '_'"),
364
category="error")
365
return flask.render_template("login.html")
366
367
if username in config.RESERVED_NAMES:
368
flask.flash(
369
Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"),
370
category="error")
371
return flask.render_template("login.html")
372
373
userCheck = User.query.filter_by(username=username).first()
374
if userCheck:
375
flask.flash(
376
Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"),
377
category="error")
378
return flask.render_template("login.html")
379
380
if password2 != password:
381
flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>Make sure the passwords match"),
382
category="error")
383
return flask.render_template("login.html")
384
385
user = User(username, password, email, name)
386
db.session.add(user)
387
db.session.commit()
388
flask.session["username"] = user.username
389
flask.flash(Markup(
390
f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully created and logged in as {username}"),
391
category="success")
392
return flask.redirect("/", code=303)
393
394
395
@app.route("/newrepo/", methods=["GET", "POST"])
396
def newRepo():
397
if flask.request.method == "GET":
398
return flask.render_template("new-repo.html")
399
else:
400
name = flask.request.form["name"]
401
visibility = int(flask.request.form["visibility"])
402
403
if not onlyChars(name, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
404
flask.flash(Markup(
405
"<iconify-icon icon='mdi:error'></iconify-icon>Repository names may only contain Latin alphabet, numbers, '-' and '_'"),
406
category="error")
407
return flask.render_template("new-repo.html")
408
409
user = User.query.filter_by(username=flask.session.get("username")).first()
410
411
repo = Repo(user, name, visibility)
412
db.session.add(repo)
413
db.session.commit()
414
415
if not os.path.exists(os.path.join(config.REPOS_PATH, repo.route)):
416
subprocess.run(["git", "init", repo.name],
417
cwd=os.path.join(config.REPOS_PATH, flask.session.get("username")))
418
419
flask.flash(Markup(f"<iconify-icon icon='mdi:folder'></iconify-icon>Successfully created repository {name}"),
420
category="success")
421
return flask.redirect(repo.route, code=303)
422
423
424
@app.route("/logout")
425
def logout():
426
flask.session.clear()
427
flask.flash(Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged out"), category="info")
428
return flask.redirect("/", code=303)
429
430
431
@app.route("/<username>/")
432
def userProfile(username):
433
user = User.query.filter_by(username=username).first()
434
repos = Repo.query.filter_by(ownerName=username, visibility=2)
435
return flask.render_template("user-profile.html", user=user, repos=repos)
436
437
438
@app.route("/<username>/<repository>/")
439
def repositoryIndex(username, repository):
440
return flask.redirect("./tree", code=302)
441
442
443
@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>")
444
def repositoryRaw(username, repository, branch, subpath):
445
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("user"), username,
446
repository) is not None):
447
flask.abort(403)
448
449
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
450
451
app.logger.info(f"Loading {serverRepoLocation}")
452
453
if not os.path.exists(serverRepoLocation):
454
app.logger.error(f"Cannot load {serverRepoLocation}")
455
return flask.render_template("not-found.html"), 404
456
457
repo = git.Repo(serverRepoLocation)
458
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
459
if not repoData.defaultBranch:
460
if repo.heads:
461
repoData.defaultBranch = repo.heads[0].name
462
else:
463
return flask.render_template("empty.html",
464
remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
465
if not branch:
466
branch = repoData.defaultBranch
467
return flask.redirect(f"./{branch}", code=302)
468
469
if branch.startswith("tag:"):
470
ref = f"tags/{branch[4:]}"
471
elif branch.startswith("~"):
472
ref = branch[1:]
473
else:
474
ref = f"heads/{branch}"
475
476
ref = ref.replace("~", "/") # encode slashes for URL support
477
478
try:
479
repo.git.checkout("-f", ref)
480
except git.exc.GitCommandError:
481
return flask.render_template("not-found.html"), 404
482
483
return flask.send_from_directory(config.REPOS_PATH, os.path.join(username, repository, subpath))
484
485
486
@app.route("/info/<username>/avatar")
487
def userAvatar(username):
488
serverUserdataLocation = os.path.join(config.USERDATA_PATH, username)
489
490
if not os.path.exists(serverUserdataLocation):
491
return flask.render_template("not-found.html"), 404
492
493
return flask.send_from_directory(serverUserdataLocation, "avatar.png")
494
495
496
@app.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""})
497
@app.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""})
498
@app.route("/<username>/<repository>/tree/<branch>/<path:subpath>")
499
def repositoryTree(username, repository, branch, subpath):
500
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
501
repository) is not None):
502
flask.abort(403)
503
504
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
505
506
app.logger.info(f"Loading {serverRepoLocation}")
507
508
if not os.path.exists(serverRepoLocation):
509
app.logger.error(f"Cannot load {serverRepoLocation}")
510
return flask.render_template("not-found.html"), 404
511
512
repo = git.Repo(serverRepoLocation)
513
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
514
if not repoData.defaultBranch:
515
if repo.heads:
516
repoData.defaultBranch = repo.heads[0].name
517
else:
518
return flask.render_template("empty.html",
519
remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
520
if not branch:
521
branch = repoData.defaultBranch
522
return flask.redirect(f"./{branch}", code=302)
523
524
if branch.startswith("tag:"):
525
ref = f"tags/{branch[4:]}"
526
elif branch.startswith("~"):
527
ref = branch[1:]
528
else:
529
ref = f"heads/{branch}"
530
531
ref = ref.replace("~", "/") # encode slashes for URL support
532
533
try:
534
repo.git.checkout("-f", ref)
535
except git.exc.GitCommandError:
536
return flask.render_template("not-found.html"), 404
537
538
branches = repo.heads
539
540
allRefs = []
541
for ref in repo.heads:
542
allRefs.append((ref, "head"))
543
for ref in repo.tags:
544
allRefs.append((ref, "tag"))
545
546
if os.path.isdir(os.path.join(serverRepoLocation, subpath)):
547
files = []
548
blobs = []
549
550
for entry in os.listdir(os.path.join(serverRepoLocation, subpath)):
551
if not os.path.basename(entry) == ".git":
552
files.append(os.path.join(subpath, entry))
553
554
infos = []
555
556
for file in files:
557
path = os.path.join(serverRepoLocation, file)
558
mimetype = guessMIME(path)
559
560
text = gitCommand(serverRepoLocation, None, "log", "--format='%H\n'", file).decode()
561
562
sha = text.split("\n")[0]
563
identifier = f"/{username}/{repository}/{sha}"
564
lastCommit = Commit.query.filter_by(identifier=identifier).first()
565
566
info = {
567
"name": os.path.basename(file),
568
"serverPath": path,
569
"relativePath": file,
570
"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file),
571
"size": humanSize(os.path.getsize(path)),
572
"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}",
573
"commit": lastCommit,
574
"shaSize": 7,
575
}
576
577
specialIcon = config.matchIcon(os.path.basename(file))
578
if specialIcon:
579
info["icon"] = specialIcon
580
elif os.path.isdir(path):
581
info["icon"] = config.folderIcon
582
elif mimetypes.guess_type(path)[0] in config.fileIcons:
583
info["icon"] = config.fileIcons[mimetypes.guess_type(path)[0]]
584
else:
585
info["icon"] = config.unknownIcon
586
587
if os.path.isdir(path):
588
infos.insert(0, info)
589
else:
590
infos.append(info)
591
592
return flask.render_template(
593
"repo-tree.html",
594
username=username,
595
repository=repository,
596
files=infos,
597
subpath=os.path.join("/", subpath),
598
branches=allRefs,
599
current=branch
600
)
601
else:
602
path = os.path.join(serverRepoLocation, subpath)
603
604
if not os.path.exists(path):
605
return flask.render_template("not-found.html"), 404
606
607
mimetype = guessMIME(path)
608
mode = mimetype.split("/", 1)[0]
609
size = humanSize(os.path.getsize(path))
610
611
specialIcon = config.matchIcon(os.path.basename(path))
612
if specialIcon:
613
icon = specialIcon
614
elif os.path.isdir(path):
615
icon = config.folderIcon
616
elif mimetypes.guess_type(path)[0] in config.fileIcons:
617
icon = config.fileIcons[mimetypes.guess_type(path)[0]]
618
else:
619
icon = config.unknownIcon
620
621
contents = None
622
if mode == "text":
623
contents = convertToHTML(path)
624
625
return flask.render_template(
626
"repo-file.html",
627
username=username,
628
repository=repository,
629
file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath),
630
branches=allRefs,
631
current=branch,
632
mode=mode,
633
mimetype=mimetype,
634
detailedtype=magic.from_file(path),
635
size=size,
636
icon=icon,
637
subpath=os.path.join("/", subpath),
638
basename=os.path.basename(path),
639
contents=contents
640
)
641
642
643
@app.route("/<username>/<repository>/forum/")
644
def repositoryForum(username, repository):
645
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
646
repository) is not None):
647
flask.abort(403)
648
649
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
650
651
app.logger.info(f"Loading {serverRepoLocation}")
652
653
if not os.path.exists(serverRepoLocation):
654
app.logger.error(f"Cannot load {serverRepoLocation}")
655
return flask.render_template("not-found.html"), 404
656
657
repo = git.Repo(serverRepoLocation)
658
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
659
user = User.query.filter_by(username=flask.session.get("username")).first()
660
relationships = RepoAccess.query.filter_by(repo=repoData)
661
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
662
663
return flask.render_template("repo-forum.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship, Post=Post)
664
665
666
@app.route("/<username>/<repository>/forum/new", methods=["POST"])
667
def repositoryForumAdd(username, repository):
668
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
669
repository) is not None):
670
flask.abort(403)
671
672
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
673
674
app.logger.info(f"Loading {serverRepoLocation}")
675
676
if not os.path.exists(serverRepoLocation):
677
app.logger.error(f"Cannot load {serverRepoLocation}")
678
return flask.render_template("not-found.html"), 404
679
680
repo = git.Repo(serverRepoLocation)
681
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
682
user = User.query.filter_by(username=flask.session.get("username")).first()
683
relationships = RepoAccess.query.filter_by(repo=repoData)
684
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
685
686
post = Post(user, repoData, None, flask.request.form["subject"], flask.request.form["message"])
687
688
db.session.add(post)
689
db.session.commit()
690
691
return flask.redirect(flask.url_for("repositoryForumThread", username=username, repository=repository, postID=post.number), code=303)
692
693
694
@app.route("/<username>/<repository>/forum/<int:postID>")
695
def repositoryForumThread(username, repository, postID):
696
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
697
repository) is not None):
698
flask.abort(403)
699
700
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
701
702
app.logger.info(f"Loading {serverRepoLocation}")
703
704
if not os.path.exists(serverRepoLocation):
705
app.logger.error(f"Cannot load {serverRepoLocation}")
706
return flask.render_template("not-found.html"), 404
707
708
repo = git.Repo(serverRepoLocation)
709
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
710
user = User.query.filter_by(username=flask.session.get("username")).first()
711
relationships = RepoAccess.query.filter_by(repo=repoData)
712
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
713
714
return flask.render_template("repo-forum-thread.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship, Post=Post, postID=postID, maxPostNesting=4)
715
716
717
@app.route("/<username>/<repository>/forum/<int:postID>/reply", methods=["POST"])
718
def repositoryForumReply(username, repository, postID):
719
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
720
repository) is not None):
721
flask.abort(403)
722
723
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
724
725
app.logger.info(f"Loading {serverRepoLocation}")
726
727
if not os.path.exists(serverRepoLocation):
728
app.logger.error(f"Cannot load {serverRepoLocation}")
729
return flask.render_template("not-found.html"), 404
730
731
repo = git.Repo(serverRepoLocation)
732
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
733
user = User.query.filter_by(username=flask.session.get("username")).first()
734
relationships = RepoAccess.query.filter_by(repo=repoData)
735
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
736
if not user:
737
flask.abort(401)
738
739
parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first()
740
post = Post(user, repoData, parent, flask.request.form["subject"], flask.request.form["message"])
741
742
db.session.add(post)
743
post.updateDate()
744
db.session.commit()
745
746
return flask.redirect(flask.url_for("repositoryForumThread", username=username, repository=repository, postID=postID), code=303)
747
748
749
@app.route("/<username>/<repository>/forum/<int:postID>/voteup", defaults={"score": 1})
750
@app.route("/<username>/<repository>/forum/<int:postID>/votedown", defaults={"score": -1})
751
@app.route("/<username>/<repository>/forum/<int:postID>/votes", defaults={"score": 0})
752
def repositoryForumVote(username, repository, postID, score):
753
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
754
repository) is not None):
755
flask.abort(403)
756
757
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
758
759
app.logger.info(f"Loading {serverRepoLocation}")
760
761
if not os.path.exists(serverRepoLocation):
762
app.logger.error(f"Cannot load {serverRepoLocation}")
763
return flask.render_template("not-found.html"), 404
764
765
repo = git.Repo(serverRepoLocation)
766
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
767
user = User.query.filter_by(username=flask.session.get("username")).first()
768
relationships = RepoAccess.query.filter_by(repo=repoData)
769
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
770
if not user:
771
flask.abort(401)
772
773
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first()
774
775
if score:
776
oldRelationship = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first()
777
if oldRelationship:
778
if score == oldRelationship.voteScore:
779
db.session.delete(oldRelationship)
780
post.voteSum -= oldRelationship.voteScore
781
else:
782
post.voteSum -= oldRelationship.voteScore
783
post.voteSum += score
784
oldRelationship.voteScore = score
785
else:
786
relationship = PostVote(user, post, score)
787
post.voteSum += score
788
db.session.add(relationship)
789
790
db.session.commit()
791
792
userVote = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first()
793
response = flask.make_response(str(post.voteSum) + " " + str(userVote.voteScore if userVote else 0))
794
response.content_type = "text/plain"
795
796
return response
797
798
799
@app.route("/<username>/<repository>/users/", methods=["GET", "POST"])
800
def repositoryUsers(username, repository):
801
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
802
repository) is not None):
803
flask.abort(403)
804
805
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
806
807
app.logger.info(f"Loading {serverRepoLocation}")
808
809
if not os.path.exists(serverRepoLocation):
810
app.logger.error(f"Cannot load {serverRepoLocation}")
811
return flask.render_template("not-found.html"), 404
812
813
repo = git.Repo(serverRepoLocation)
814
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
815
user = User.query.filter_by(username=flask.session.get("username")).first()
816
relationships = RepoAccess.query.filter_by(repo=repoData)
817
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
818
819
if flask.request.method == "GET":
820
return flask.render_template("repo-users.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship)
821
else:
822
if getPermissionLevel(flask.session.get("username"), username, repository) != 2:
823
flask.abort(401)
824
825
if flask.request.form.get("new-username"):
826
# Create new relationship
827
newUser = User.query.filter_by(username=flask.request.form.get("new-username")).first()
828
relationship = RepoAccess(newUser, repoData, flask.request.form.get("new-level"))
829
db.session.add(relationship)
830
db.session.commit()
831
if flask.request.form.get("update-username"):
832
# Create new relationship
833
updatedUser = User.query.filter_by(username=flask.request.form.get("update-username")).first()
834
relationship = RepoAccess.query.filter_by(repo=repoData, user=updatedUser).first()
835
if flask.request.form.get("update-level") == -1:
836
relationship.delete()
837
else:
838
relationship.accessLevel = flask.request.form.get("update-level")
839
db.session.commit()
840
841
return flask.redirect(app.url_for("repositoryUsers", username=username, repository=repository))
842
843
844
@app.route("/<username>/<repository>/branches/")
845
def repositoryBranches(username, repository):
846
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
847
repository) is not None):
848
flask.abort(403)
849
850
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
851
852
app.logger.info(f"Loading {serverRepoLocation}")
853
854
if not os.path.exists(serverRepoLocation):
855
app.logger.error(f"Cannot load {serverRepoLocation}")
856
return flask.render_template("not-found.html"), 404
857
858
repo = git.Repo(serverRepoLocation)
859
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
860
861
return flask.render_template("repo-branches.html", username=username, repository=repository, repoData=repoData, repo=repo)
862
863
864
@app.route("/<username>/<repository>/log/")
865
def repositoryLog(username, repository):
866
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
867
repository) is not None):
868
flask.abort(403)
869
870
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
871
872
app.logger.info(f"Loading {serverRepoLocation}")
873
874
if not os.path.exists(serverRepoLocation):
875
app.logger.error(f"Cannot load {serverRepoLocation}")
876
return flask.render_template("not-found.html"), 404
877
878
repo = git.Repo(serverRepoLocation)
879
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
880
commits = Commit.query.filter_by(repo=repoData)
881
882
return flask.render_template("repo-log.html", username=username, repository=repository, repoData=repoData, repo=repo, commits=commits)
883
884
885
@app.route("/<username>/<repository>/settings/")
886
def repositorySettings(username, repository):
887
if getPermissionLevel(flask.session.get("username"), username, repository) != 2:
888
flask.abort(401)
889
890
return flask.render_template("repo-settings.html", username=username, repository=repository)
891
892
893
@app.errorhandler(404)
894
def e404(error):
895
return flask.render_template("not-found.html"), 404
896
897
898
@app.errorhandler(401)
899
def e401(error):
900
return flask.render_template("unauthorised.html"), 401
901
902
903
@app.errorhandler(403)
904
def e403(error):
905
return flask.render_template("forbidden.html"), 403
906
907
908
@app.errorhandler(418)
909
def e418(error):
910
return flask.render_template("teapot.html"), 418
911
912
913
@app.errorhandler(405)
914
def e405(error):
915
return flask.render_template("method-not-allowed.html"), 405
916
917
918
if __name__ == "__main__":
919
app.run(debug=True, port=8080, host="0.0.0.0")
920