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