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.77 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, relationship=oldRelationship)
308
case "follows":
309
return flask.render_template("user-profile-follows.html", user=user, relationship=oldRelationship)
310
case _:
311
return flask.render_template("user-profile-overview.html", user=user, relationship=oldRelationship)
312
313
elif flask.request.method == "POST":
314
match flask.request.args.get("action"):
315
case "follow":
316
if oldRelationship:
317
db.session.delete(oldRelationship)
318
else:
319
relationship = UserFollow(
320
flask.session.get("username"),
321
username
322
)
323
db.session.add(relationship)
324
325
db.session.commit()
326
return flask.redirect("?", code=303)
327
328
329
@app.route("/<username>/<repository>/")
330
def repositoryIndex(username, repository):
331
return flask.redirect("./tree", code=302)
332
333
334
@app.route("/info/<username>/avatar")
335
def userAvatar(username):
336
serverUserdataLocation = os.path.join(config.USERDATA_PATH, username)
337
338
if not os.path.exists(serverUserdataLocation):
339
return flask.render_template("not-found.html"), 404
340
341
return flask.send_from_directory(serverUserdataLocation, "avatar.png")
342
343
344
@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>")
345
def repositoryRaw(username, repository, branch, subpath):
346
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
347
repository) is not None):
348
flask.abort(403)
349
350
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
351
352
app.logger.info(f"Loading {serverRepoLocation}")
353
354
if not os.path.exists(serverRepoLocation):
355
app.logger.error(f"Cannot load {serverRepoLocation}")
356
return flask.render_template("not-found.html"), 404
357
358
repo = git.Repo(serverRepoLocation)
359
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
360
if not repoData.defaultBranch:
361
if repo.heads:
362
repoData.defaultBranch = repo.heads[0].name
363
else:
364
return flask.render_template("empty.html",
365
remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
366
if not branch:
367
branch = repoData.defaultBranch
368
return flask.redirect(f"./{branch}", code=302)
369
370
if branch.startswith("tag:"):
371
ref = f"tags/{branch[4:]}"
372
elif branch.startswith("~"):
373
ref = branch[1:]
374
else:
375
ref = f"heads/{branch}"
376
377
ref = ref.replace("~", "/") # encode slashes for URL support
378
379
try:
380
repo.git.checkout("-f", ref)
381
except git.exc.GitCommandError:
382
return flask.render_template("not-found.html"), 404
383
384
return flask.send_from_directory(config.REPOS_PATH, os.path.join(username, repository, subpath))
385
386
387
@repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""})
388
@repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""})
389
@repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>")
390
def repositoryTree(username, repository, branch, subpath):
391
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
392
repository) is not None):
393
flask.abort(403)
394
395
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
396
397
app.logger.info(f"Loading {serverRepoLocation}")
398
399
if not os.path.exists(serverRepoLocation):
400
app.logger.error(f"Cannot load {serverRepoLocation}")
401
return flask.render_template("not-found.html"), 404
402
403
repo = git.Repo(serverRepoLocation)
404
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
405
if not repoData.defaultBranch:
406
if repo.heads:
407
repoData.defaultBranch = repo.heads[0].name
408
else:
409
return flask.render_template("empty.html",
410
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
411
if not branch:
412
branch = repoData.defaultBranch
413
return flask.redirect(f"./{branch}", code=302)
414
415
if branch.startswith("tag:"):
416
ref = f"tags/{branch[4:]}"
417
elif branch.startswith("~"):
418
ref = branch[1:]
419
else:
420
ref = f"heads/{branch}"
421
422
ref = ref.replace("~", "/") # encode slashes for URL support
423
424
try:
425
repo.git.checkout("-f", ref)
426
except git.exc.GitCommandError:
427
return flask.render_template("not-found.html"), 404
428
429
branches = repo.heads
430
431
allRefs = []
432
for ref in repo.heads:
433
allRefs.append((ref, "head"))
434
for ref in repo.tags:
435
allRefs.append((ref, "tag"))
436
437
if os.path.isdir(os.path.join(serverRepoLocation, subpath)):
438
files = []
439
blobs = []
440
441
for entry in os.listdir(os.path.join(serverRepoLocation, subpath)):
442
if not os.path.basename(entry) == ".git":
443
files.append(os.path.join(subpath, entry))
444
445
infos = []
446
447
for file in files:
448
path = os.path.join(serverRepoLocation, file)
449
mimetype = guessMIME(path)
450
451
text = gitCommand(serverRepoLocation, None, "log", "--format='%H\n'", file).decode()
452
453
sha = text.split("\n")[0]
454
identifier = f"/{username}/{repository}/{sha}"
455
lastCommit = Commit.query.filter_by(identifier=identifier).first()
456
457
info = {
458
"name": os.path.basename(file),
459
"serverPath": path,
460
"relativePath": file,
461
"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file),
462
"size": humanSize(os.path.getsize(path)),
463
"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}",
464
"commit": lastCommit,
465
"shaSize": 7,
466
}
467
468
specialIcon = config.matchIcon(os.path.basename(file))
469
if specialIcon:
470
info["icon"] = specialIcon
471
elif os.path.isdir(path):
472
info["icon"] = config.folderIcon
473
elif mimetypes.guess_type(path)[0] in config.fileIcons:
474
info["icon"] = config.fileIcons[mimetypes.guess_type(path)[0]]
475
else:
476
info["icon"] = config.unknownIcon
477
478
if os.path.isdir(path):
479
infos.insert(0, info)
480
else:
481
infos.append(info)
482
483
return flask.render_template(
484
"repo-tree.html",
485
username=username,
486
repository=repository,
487
files=infos,
488
subpath=os.path.join("/", subpath),
489
branches=allRefs,
490
current=branch,
491
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
492
isFavourite=getFavourite(flask.session.get("username"), username, repository)
493
)
494
else:
495
path = os.path.join(serverRepoLocation, subpath)
496
497
if not os.path.exists(path):
498
return flask.render_template("not-found.html"), 404
499
500
mimetype = guessMIME(path)
501
mode = mimetype.split("/", 1)[0]
502
size = humanSize(os.path.getsize(path))
503
504
specialIcon = config.matchIcon(os.path.basename(path))
505
if specialIcon:
506
icon = specialIcon
507
elif os.path.isdir(path):
508
icon = config.folderIcon
509
elif mimetypes.guess_type(path)[0] in config.fileIcons:
510
icon = config.fileIcons[mimetypes.guess_type(path)[0]]
511
else:
512
icon = config.unknownIcon
513
514
contents = None
515
if mode == "text":
516
contents = convertToHTML(path)
517
518
return flask.render_template(
519
"repo-file.html",
520
username=username,
521
repository=repository,
522
file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath),
523
branches=allRefs,
524
current=branch,
525
mode=mode,
526
mimetype=mimetype,
527
detailedtype=magic.from_file(path),
528
size=size,
529
icon=icon,
530
subpath=os.path.join("/", subpath),
531
basename=os.path.basename(path),
532
contents=contents,
533
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
534
isFavourite=getFavourite(flask.session.get("username"), username, repository)
535
)
536
537
538
@repositories.route("/<username>/<repository>/forum/")
539
def repositoryForum(username, repository):
540
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
541
repository) is not None):
542
flask.abort(403)
543
544
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
545
546
app.logger.info(f"Loading {serverRepoLocation}")
547
548
if not os.path.exists(serverRepoLocation):
549
app.logger.error(f"Cannot load {serverRepoLocation}")
550
return flask.render_template("not-found.html"), 404
551
552
repo = git.Repo(serverRepoLocation)
553
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
554
user = User.query.filter_by(username=flask.session.get("username")).first()
555
relationships = RepoAccess.query.filter_by(repo=repoData)
556
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
557
558
return flask.render_template(
559
"repo-forum.html",
560
username=username,
561
repository=repository,
562
repoData=repoData,
563
relationships=relationships,
564
repo=repo,
565
userRelationship=userRelationship,
566
Post=Post,
567
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
568
isFavourite=getFavourite(flask.session.get("username"), username, repository)
569
)
570
571
572
@repositories.route("/<username>/<repository>/forum/new", methods=["POST"])
573
def repositoryForumAdd(username, repository):
574
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
575
repository) is not None):
576
flask.abort(403)
577
578
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
579
580
app.logger.info(f"Loading {serverRepoLocation}")
581
582
if not os.path.exists(serverRepoLocation):
583
app.logger.error(f"Cannot load {serverRepoLocation}")
584
return flask.render_template("not-found.html"), 404
585
586
repo = git.Repo(serverRepoLocation)
587
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
588
user = User.query.filter_by(username=flask.session.get("username")).first()
589
relationships = RepoAccess.query.filter_by(repo=repoData)
590
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
591
592
post = Post(user, repoData, None, flask.request.form["subject"], flask.request.form["message"])
593
594
db.session.add(post)
595
db.session.commit()
596
597
return flask.redirect(flask.url_for(".repositoryForumThread", username=username, repository=repository, postID=post.number), code=303)
598
599
600
@repositories.route("/<username>/<repository>/forum/<int:postID>")
601
def repositoryForumThread(username, repository, postID):
602
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
603
repository) is not None):
604
flask.abort(403)
605
606
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
607
608
app.logger.info(f"Loading {serverRepoLocation}")
609
610
if not os.path.exists(serverRepoLocation):
611
app.logger.error(f"Cannot load {serverRepoLocation}")
612
return flask.render_template("not-found.html"), 404
613
614
repo = git.Repo(serverRepoLocation)
615
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
616
user = User.query.filter_by(username=flask.session.get("username")).first()
617
relationships = RepoAccess.query.filter_by(repo=repoData)
618
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
619
620
return flask.render_template(
621
"repo-forum-thread.html",
622
username=username,
623
repository=repository,
624
repoData=repoData,
625
relationships=relationships,
626
repo=repo,
627
userRelationship=userRelationship,
628
Post=Post,
629
postID=postID,
630
maxPostNesting=4,
631
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
632
isFavourite=getFavourite(flask.session.get("username"), username, repository)
633
)
634
635
636
@repositories.route("/<username>/<repository>/forum/<int:postID>/reply", methods=["POST"])
637
def repositoryForumReply(username, repository, postID):
638
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
639
repository) is not None):
640
flask.abort(403)
641
642
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
643
644
app.logger.info(f"Loading {serverRepoLocation}")
645
646
if not os.path.exists(serverRepoLocation):
647
app.logger.error(f"Cannot load {serverRepoLocation}")
648
return flask.render_template("not-found.html"), 404
649
650
repo = git.Repo(serverRepoLocation)
651
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
652
user = User.query.filter_by(username=flask.session.get("username")).first()
653
relationships = RepoAccess.query.filter_by(repo=repoData)
654
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
655
if not user:
656
flask.abort(401)
657
658
parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first()
659
post = Post(user, repoData, parent, flask.request.form["subject"], flask.request.form["message"])
660
661
db.session.add(post)
662
post.updateDate()
663
db.session.commit()
664
665
return flask.redirect(flask.url_for(".repositoryForumThread", username=username, repository=repository, postID=postID), code=303)
666
667
668
@app.route("/<username>/<repository>/forum/<int:postID>/voteup", defaults={"score": 1})
669
@app.route("/<username>/<repository>/forum/<int:postID>/votedown", defaults={"score": -1})
670
@app.route("/<username>/<repository>/forum/<int:postID>/votes", defaults={"score": 0})
671
def repositoryForumVote(username, repository, postID, score):
672
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
673
repository) is not None):
674
flask.abort(403)
675
676
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
677
678
app.logger.info(f"Loading {serverRepoLocation}")
679
680
if not os.path.exists(serverRepoLocation):
681
app.logger.error(f"Cannot load {serverRepoLocation}")
682
return flask.render_template("not-found.html"), 404
683
684
repo = git.Repo(serverRepoLocation)
685
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
686
user = User.query.filter_by(username=flask.session.get("username")).first()
687
relationships = RepoAccess.query.filter_by(repo=repoData)
688
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
689
if not user:
690
flask.abort(401)
691
692
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first()
693
694
if score:
695
oldRelationship = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first()
696
if oldRelationship:
697
if score == oldRelationship.voteScore:
698
db.session.delete(oldRelationship)
699
post.voteSum -= oldRelationship.voteScore
700
else:
701
post.voteSum -= oldRelationship.voteScore
702
post.voteSum += score
703
oldRelationship.voteScore = score
704
else:
705
relationship = PostVote(user, post, score)
706
post.voteSum += score
707
db.session.add(relationship)
708
709
db.session.commit()
710
711
userVote = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first()
712
response = flask.make_response(str(post.voteSum) + " " + str(userVote.voteScore if userVote else 0))
713
response.content_type = "text/plain"
714
715
return response
716
717
718
@app.route("/<username>/<repository>/favourite")
719
def repositoryFavourite(username, repository):
720
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
721
repository) is not None):
722
flask.abort(403)
723
724
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
725
726
app.logger.info(f"Loading {serverRepoLocation}")
727
728
if not os.path.exists(serverRepoLocation):
729
app.logger.error(f"Cannot load {serverRepoLocation}")
730
return flask.render_template("not-found.html"), 404
731
732
repo = git.Repo(serverRepoLocation)
733
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
734
user = User.query.filter_by(username=flask.session.get("username")).first()
735
relationships = RepoAccess.query.filter_by(repo=repoData)
736
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
737
if not user:
738
flask.abort(401)
739
740
oldRelationship = RepoFavourite.query.filter_by(userUsername=user.username, repoRoute=repoData.route).first()
741
if oldRelationship:
742
db.session.delete(oldRelationship)
743
else:
744
relationship = RepoFavourite(user, repoData)
745
db.session.add(relationship)
746
747
db.session.commit()
748
749
return flask.redirect(flask.url_for("favourites"), code=303)
750
751
752
@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"])
753
def repositoryUsers(username, repository):
754
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
755
repository) is not None):
756
flask.abort(403)
757
758
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
759
760
app.logger.info(f"Loading {serverRepoLocation}")
761
762
if not os.path.exists(serverRepoLocation):
763
app.logger.error(f"Cannot load {serverRepoLocation}")
764
return flask.render_template("not-found.html"), 404
765
766
repo = git.Repo(serverRepoLocation)
767
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
768
user = User.query.filter_by(username=flask.session.get("username")).first()
769
relationships = RepoAccess.query.filter_by(repo=repoData)
770
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
771
772
if flask.request.method == "GET":
773
return flask.render_template(
774
"repo-users.html",
775
username=username,
776
repository=repository,
777
repoData=repoData,
778
relationships=relationships,
779
repo=repo,
780
userRelationship=userRelationship,
781
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
782
isFavourite=getFavourite(flask.session.get("username"), username, repository)
783
)
784
else:
785
if getPermissionLevel(flask.session.get("username"), username, repository) != 2:
786
flask.abort(401)
787
788
if flask.request.form.get("new-username"):
789
# Create new relationship
790
newUser = User.query.filter_by(username=flask.request.form.get("new-username")).first()
791
relationship = RepoAccess(newUser, repoData, flask.request.form.get("new-level"))
792
db.session.add(relationship)
793
db.session.commit()
794
if flask.request.form.get("update-username"):
795
# Create new relationship
796
updatedUser = User.query.filter_by(username=flask.request.form.get("update-username")).first()
797
relationship = RepoAccess.query.filter_by(repo=repoData, user=updatedUser).first()
798
if flask.request.form.get("update-level") == -1:
799
relationship.delete()
800
else:
801
relationship.accessLevel = flask.request.form.get("update-level")
802
db.session.commit()
803
804
return flask.redirect(app.url_for(".repositoryUsers", username=username, repository=repository))
805
806
807
@repositories.route("/<username>/<repository>/branches/")
808
def repositoryBranches(username, repository):
809
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
810
repository) is not None):
811
flask.abort(403)
812
813
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
814
815
app.logger.info(f"Loading {serverRepoLocation}")
816
817
if not os.path.exists(serverRepoLocation):
818
app.logger.error(f"Cannot load {serverRepoLocation}")
819
return flask.render_template("not-found.html"), 404
820
821
repo = git.Repo(serverRepoLocation)
822
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
823
824
return flask.render_template(
825
"repo-branches.html",
826
username=username,
827
repository=repository,
828
repoData=repoData,
829
repo=repo,
830
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
831
isFavourite=getFavourite(flask.session.get("username"), username, repository)
832
)
833
834
835
@repositories.route("/<username>/<repository>/log/", defaults={"branch": None})
836
@repositories.route("/<username>/<repository>/log/<branch>/")
837
def repositoryLog(username, repository, branch):
838
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
839
repository) is not None):
840
flask.abort(403)
841
842
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
843
844
app.logger.info(f"Loading {serverRepoLocation}")
845
846
if not os.path.exists(serverRepoLocation):
847
app.logger.error(f"Cannot load {serverRepoLocation}")
848
return flask.render_template("not-found.html"), 404
849
850
repo = git.Repo(serverRepoLocation)
851
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
852
if not repoData.defaultBranch:
853
if repo.heads:
854
repoData.defaultBranch = repo.heads[0].name
855
else:
856
return flask.render_template("empty.html",
857
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
858
if not branch:
859
branch = repoData.defaultBranch
860
return flask.redirect(f"./{branch}", code=302)
861
862
if branch.startswith("tag:"):
863
ref = f"tags/{branch[4:]}"
864
elif branch.startswith("~"):
865
ref = branch[1:]
866
else:
867
ref = f"heads/{branch}"
868
869
ref = ref.replace("~", "/") # encode slashes for URL support
870
871
try:
872
repo.git.checkout("-f", ref)
873
except git.exc.GitCommandError:
874
return flask.render_template("not-found.html"), 404
875
876
branches = repo.heads
877
878
allRefs = []
879
for ref in repo.heads:
880
allRefs.append((ref, "head"))
881
for ref in repo.tags:
882
allRefs.append((ref, "tag"))
883
884
commitList = [f"/{username}/{repository}/{sha}" for sha in gitCommand(serverRepoLocation, None, "log", "--format='%H'").decode().split("\n")]
885
886
commits = Commit.query.filter(Commit.identifier.in_(commitList))
887
888
return flask.render_template(
889
"repo-log.html",
890
username=username,
891
repository=repository,
892
branches=allRefs,
893
current=branch,
894
repoData=repoData,
895
repo=repo,
896
commits=commits,
897
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
898
isFavourite=getFavourite(flask.session.get("username"), username, repository)
899
)
900
901
902
@repositories.route("/<username>/<repository>/settings/")
903
def repositorySettings(username, repository):
904
if getPermissionLevel(flask.session.get("username"), username, repository) != 2:
905
flask.abort(401)
906
907
return flask.render_template("repo-settings.html", username=username, repository=repository)
908
909
910
@app.errorhandler(404)
911
def e404(error):
912
return flask.render_template("not-found.html"), 404
913
914
915
@app.errorhandler(401)
916
def e401(error):
917
return flask.render_template("unauthorised.html"), 401
918
919
920
@app.errorhandler(403)
921
def e403(error):
922
return flask.render_template("forbidden.html"), 403
923
924
925
@app.errorhandler(418)
926
def e418(error):
927
return flask.render_template("teapot.html"), 418
928
929
930
@app.errorhandler(405)
931
def e405(error):
932
return flask.render_template("method-not-allowed.html"), 405
933
934
935
if __name__ == "__main__":
936
app.run(debug=True, port=8080, host="0.0.0.0")
937
938
939
app.register_blueprint(repositories)
940