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