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