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