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