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