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