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