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 • 78.1 kiB
Python script, Unicode text, UTF-8 text executable
        
            
1
__version__ = "0.5.0"
2
3
import os
4
import shutil
5
import random
6
import subprocess
7
import platform
8
9
import PIL
10
import git
11
import mimetypes
12
import magic
13
import flask
14
import cairosvg
15
import celery
16
import shlex
17
from functools import wraps
18
from datetime import datetime
19
from enum import Enum
20
from cairosvg import svg2png
21
from flask_sqlalchemy import SQLAlchemy
22
from flask_bcrypt import Bcrypt
23
from markupsafe import escape, Markup
24
from flask_migrate import Migrate
25
from PIL import Image
26
from flask_httpauth import HTTPBasicAuth
27
import config
28
import markdown
29
from common import git_command
30
from flask_babel import Babel, gettext, ngettext, force_locale
31
from jinja2_fragments.flask import render_block
32
33
import logging
34
35
36
class No304(logging.Filter):
37
def filter(self, record):
38
return not record.getMessage().strip().endswith("304 -")
39
40
41
logging.getLogger("werkzeug").addFilter(No304())
42
43
_ = gettext
44
n_ = ngettext
45
46
app = flask.Flask(__name__)
47
app.config.from_mapping(
48
CELERY=dict(
49
broker_url=config.REDIS_URI,
50
result_backend=config.REDIS_URI,
51
task_ignore_result=True,
52
),
53
)
54
55
auth = HTTPBasicAuth()
56
57
app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI
58
app.config["SECRET_KEY"] = config.DB_PASSWORD
59
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
60
app.config["BABEL_TRANSLATION_DIRECTORIES"] = "i18n"
61
app.config["MAX_CONTENT_LENGTH"] = config.MAX_PAYLOAD_SIZE
62
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
63
app.config["SESSION_COOKIE_SECURE"] = config.suggest_https # only send cookies over HTTPS if the server is configured for it
64
app.config["SESSION_COOKIE_HTTPONLY"] = True # don't allow JS to access the cookie
65
if config.restrict_cookie_domain:
66
app.config["SESSION_COOKIE_DOMAIN"] = config.BASE_DOMAIN # don't share across subdomains, since user content is hosted there
67
68
db = SQLAlchemy(app)
69
bcrypt = Bcrypt(app)
70
migrate = Migrate(app, db)
71
72
from misc_utils import *
73
74
import git_http
75
import api
76
import jinja_utils
77
import celery_tasks
78
from celery import Celery, Task
79
import celery_integration
80
import pathlib
81
82
from models import *
83
84
babel = Babel(app)
85
86
87
def get_locale():
88
if flask.request.cookies.get("language"):
89
return flask.request.cookies.get("language")
90
return flask.request.accept_languages.best_match(config.available_locales)
91
92
93
babel.init_app(app, locale_selector=get_locale)
94
95
with app.app_context():
96
locale_names = {}
97
for language in config.available_locales:
98
with force_locale(language):
99
# NOTE: Translate this to the language's name in that language, for example in French you would use français
100
locale_names[language] = gettext("English")
101
102
worker = celery_integration.init_celery_app(app)
103
104
repositories = flask.Blueprint("repository", __name__, template_folder="templates/repository/")
105
106
app.jinja_env.add_extension("jinja2.ext.do")
107
app.jinja_env.add_extension("jinja2.ext.loopcontrols")
108
app.jinja_env.add_extension("jinja2.ext.debug")
109
110
111
@app.context_processor
112
def default():
113
username = flask.session.get("username")
114
115
user_object = User.query.filter_by(username=username).first()
116
117
return {
118
"logged_in_user": username,
119
"user_object": user_object,
120
"Notification": Notification,
121
"unread": UserNotification.query.filter_by(user_username=username).filter(
122
UserNotification.attention_level > 0).count(),
123
"config": config,
124
"Markup": Markup,
125
"locale_names": locale_names,
126
"set": set, # since using {} is impossible in Jinja
127
"request": flask.request,
128
"get_visibility": get_visibility,
129
"get_permission_level": get_permission_level,
130
}
131
132
133
@app.route("/")
134
def main():
135
if flask.session.get("username"):
136
return flask.render_template("home.html")
137
else:
138
return flask.render_template("no-home.html")
139
140
141
@app.route("/userstyle")
142
def userstyle():
143
if flask.session.get("username") and os.path.exists(
144
os.path.join(config.REPOS_PATH, flask.session.get("username"), ".config",
145
"theme.css")):
146
return flask.send_from_directory(
147
os.path.join(config.REPOS_PATH, flask.session.get("username"), ".config"),
148
"theme.css")
149
else:
150
return flask.Response("", mimetype="text/css")
151
152
153
@app.route("/about/")
154
def about():
155
return flask.render_template("about.html", platform=platform, version=__version__)
156
157
158
@app.route("/search")
159
def search():
160
query = flask.request.args.get("q")
161
page_number = flask.request.args.get("page", 1, type=int)
162
if flask.session.get("username"):
163
default_page_length = db.session.get(User, flask.session.get("username")).default_page_length
164
else:
165
default_page_length = 16
166
page_length = flask.request.args.get("per_page", default_page_length, type=int)
167
if not query:
168
flask.abort(400)
169
170
results = Repo.query.filter(Repo.name.ilike(f"%{query}%")).filter_by(visibility=2).paginate(
171
page=page_number, per_page=page_length)
172
173
if results.has_next:
174
next_page = results.next_num
175
else:
176
next_page = None
177
178
if results.has_prev:
179
prev_page = results.prev_num
180
else:
181
prev_page = None
182
183
return flask.render_template("search.html", results=results, query=query,
184
page_number=page_number,
185
page_length=page_length,
186
next_page=next_page,
187
prev_page=prev_page,
188
num_pages=results.pages)
189
190
191
@app.route("/language", methods=["POST"])
192
def set_locale():
193
response = flask.redirect(flask.request.referrer if flask.request.referrer else "/",
194
code=303)
195
if not flask.request.form.get("language"):
196
response.delete_cookie("language")
197
else:
198
response.set_cookie("language", flask.request.form.get("language"))
199
200
return response
201
202
203
@app.route("/cookie-dismiss")
204
def dismiss_banner():
205
response = flask.redirect(flask.request.referrer if flask.request.referrer else "/",
206
code=303)
207
response.set_cookie("cookie-banner", "1")
208
return response
209
210
211
@app.route("/help/")
212
def help_redirect():
213
return flask.redirect(config.help_url, code=302)
214
215
216
@app.route("/settings/")
217
def settings():
218
if not flask.session.get("username"):
219
flask.abort(401)
220
user = User.query.filter_by(username=flask.session.get("username")).first()
221
222
return flask.render_template("user-settings.html", user=user)
223
224
225
@app.route("/settings/confirm-email/<code>")
226
def confirm_email(code):
227
request = EmailChangeRequest.query.filter_by(code=code).first()
228
if not request:
229
flask.abort(404)
230
231
user = db.session.get(User, request.user_username)
232
user.email = request.new_email
233
db.session.delete(request)
234
db.session.commit()
235
236
return flask.redirect("/settings", code=303)
237
238
239
@app.route("/settings/profile", methods=["POST"])
240
def settings_profile():
241
user = User.query.filter_by(username=flask.session.get("username")).first()
242
243
user.display_name = flask.request.form["displayname"]
244
user.URL = flask.request.form["url"]
245
user.company = flask.request.form["company"]
246
user.company_URL = flask.request.form["companyurl"]
247
if not flask.request.form.get("email"):
248
# Deleting the email can be instant; no need to confirm
249
user.email = ""
250
elif flask.request.form.get("email") != user.email:
251
# Changing the email requires confirmation from the address holder
252
celery_tasks.request_email_change.delay(user.username, flask.request.form["email"])
253
user.location = flask.request.form["location"]
254
user.show_mail = True if flask.request.form.get("showmail") else False
255
user.bio = flask.request.form.get("bio")
256
257
db.session.commit()
258
259
flask.flash(
260
Markup("<iconify-icon icon='mdi:check'></iconify-icon>" + _("Settings saved")),
261
category="success")
262
return flask.redirect(f"/{flask.session.get('username')}", code=303)
263
264
265
@app.route("/settings/preferences", methods=["POST"])
266
def settings_prefs():
267
user = User.query.filter_by(username=flask.session.get("username")).first()
268
269
user.default_page_length = int(flask.request.form["page_length"])
270
user.max_post_nesting = int(flask.request.form["max_post_nesting"])
271
272
db.session.commit()
273
274
flask.flash(
275
Markup("<iconify-icon icon='mdi:check'></iconify-icon>" + _("Settings saved")),
276
category="success")
277
return flask.redirect(f"/{flask.session.get('username')}", code=303)
278
279
280
@app.route("/favourites/", methods=["GET", "POST"])
281
def favourites():
282
if not flask.session.get("username"):
283
flask.abort(401)
284
if flask.request.method == "GET":
285
relationships = RepoFavourite.query.filter_by(
286
user_username=flask.session.get("username"))
287
288
return flask.render_template("favourites.html", favourites=relationships)
289
290
291
@app.route("/favourites/<int:id>", methods=["POST"])
292
def favourite_edit(id):
293
if not flask.session.get("username"):
294
flask.abort(401)
295
favourite = db.session.get(RepoFavourite, id)
296
if favourite.user_username != flask.session.get("username"):
297
flask.abort(403)
298
data = flask.request.form
299
favourite.notify_commit = js_to_bool(data.get("commit"))
300
favourite.notify_forum = js_to_bool(data.get("forum"))
301
favourite.notify_pr = js_to_bool(data.get("pull_request"))
302
favourite.notify_admin = js_to_bool(data.get("administrative"))
303
db.session.commit()
304
return flask.render_template_string(
305
"""
306
<tr hx-post="/favourites/{{ favourite.id }}" hx-trigger="change" hx-include="#commit-{{ favourite.id }}, #forum-{{ favourite.id }}, #pull_request-{{ favourite.id }}, #administrative-{{ favourite.id }}" hx-headers='{"Content-Type": "application/json"}' hx-swap="outerHTML">
307
<td><a href="{{ favourite.repo.route }}">{{ favourite.repo.owner.username }}/{{ favourite.repo.name }}</a></td>
308
<td style="text-align: center;"><input type="checkbox" name="commit" id="commit-{{ favourite.id }}" value="true" {% if favourite.notify_commit %}checked{% endif %}></td>
309
<td style="text-align: center;"><input type="checkbox" name="forum" id="forum-{{ favourite.id }}" value="true" {% if favourite.notify_forum %}checked{% endif %}></td>
310
<td style="text-align: center;"><input type="checkbox" name="pull_request" id="pull_request-{{ favourite.id }}" value="true" {% if favourite.notify_pr %}checked{% endif %}></td>
311
<td style="text-align: center;"><input type="checkbox" name="administrative" id="administrative-{{ favourite.id }}" value="true" {% if favourite.notify_admin %}checked{% endif %}></td>
312
</tr>
313
""",
314
favourite=favourite
315
)
316
317
318
@app.route("/notifications/", methods=["GET", "POST"])
319
def notifications():
320
if not flask.session.get("username"):
321
flask.abort(401)
322
if flask.request.method == "GET":
323
page_number = flask.request.args.get("page", 1, type=int)
324
if flask.session.get("username"):
325
default_page_length = db.session.get(User, flask.session.get(
326
"username")).default_page_length
327
else:
328
default_page_length = 16
329
page_length = flask.request.args.get("per_page", default_page_length, type=int)
330
331
results = UserNotification.query.filter_by(
332
user_username=flask.session.get("username")).order_by(UserNotification.id.desc()).paginate(
333
page=page_number, per_page=page_length)
334
335
if results.has_next:
336
next_page = results.next_num
337
else:
338
next_page = None
339
340
if results.has_prev:
341
prev_page = results.prev_num
342
else:
343
prev_page = None
344
345
return flask.render_template("notifications.html",
346
notifications=results,
347
db=db, Commit=Commit, Post=Post, PullRequest=PullRequest, User=User,
348
page_number=page_number,
349
page_length=page_length,
350
next_page=next_page,
351
prev_page=prev_page,
352
num_pages=results.pages
353
)
354
355
356
@app.route("/notifications/<int:notification_id>/read", methods=["POST"])
357
def mark_read(notification_id):
358
if not flask.session.get("username"):
359
flask.abort(401)
360
notification = UserNotification.query.filter_by(id=notification_id).first()
361
if notification.user_username != flask.session.get("username"):
362
flask.abort(403)
363
notification.mark_read()
364
db.session.commit()
365
return flask.render_template_string(
366
"<button hx-post='/notifications/{{ notification.id }}/unread' hx-swap='outerHTML'>Mark as unread</button>",
367
notification=notification), 200
368
369
370
@app.route("/notifications/<int:notification_id>/unread", methods=["POST"])
371
def mark_unread(notification_id):
372
if not flask.session.get("username"):
373
flask.abort(401)
374
notification = UserNotification.query.filter_by(id=notification_id).first()
375
if notification.user_username != flask.session.get("username"):
376
flask.abort(403)
377
notification.mark_unread()
378
db.session.commit()
379
return flask.render_template_string(
380
"<button hx-post='/notifications/{{ notification.id }}/read' hx-swap='outerHTML'>Mark as read</button>",
381
notification=notification), 200
382
383
384
@app.route("/notifications/mark-all-read", methods=["POST"])
385
def mark_all_read():
386
if not flask.session.get("username"):
387
flask.abort(401)
388
389
notifications = UserNotification.query.filter_by(
390
user_username=flask.session.get("username"))
391
for notification in notifications:
392
notification.mark_read()
393
db.session.commit()
394
return flask.redirect("/notifications/", code=303)
395
396
397
@app.route("/accounts/", methods=["GET", "POST"])
398
def login():
399
if flask.request.method == "GET":
400
return flask.render_template("login.html")
401
else:
402
if "login" in flask.request.form:
403
username = flask.request.form["username"]
404
password = flask.request.form["password"]
405
406
user = User.query.filter_by(username=username).first()
407
408
if user and bcrypt.check_password_hash(user.password_hashed, password):
409
flask.session["username"] = user.username
410
flask.flash(
411
Markup("<iconify-icon icon='mdi:account'></iconify-icon>" + _(
412
"Successfully logged in as {username}").format(
413
username=username)),
414
category="success")
415
return flask.redirect("/", code=303)
416
elif not user:
417
flask.flash(Markup(
418
"<iconify-icon icon='mdi:account-question'></iconify-icon>" + _(
419
"User not found")),
420
category="alert")
421
return flask.render_template("login.html")
422
else:
423
flask.flash(Markup(
424
"<iconify-icon icon='mdi:account-question'></iconify-icon>" + _(
425
"Invalid password")),
426
category="error")
427
return flask.render_template("login.html")
428
if "signup" in flask.request.form:
429
username = flask.request.form["username"]
430
password = flask.request.form["password"]
431
password2 = flask.request.form["password2"]
432
email = flask.request.form.get("email")
433
email2 = flask.request.form.get("email2") # repeat email is a honeypot
434
name = flask.request.form.get("name")
435
436
if not only_chars(username,
437
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"):
438
flask.flash(Markup(
439
_("Usernames may only contain Latin alphabet, numbers and '-'")),
440
category="error")
441
return flask.render_template("login.html")
442
if "--" in username:
443
flask.flash(Markup(
444
_("Usernames may not contain consecutive hyphens")),
445
category="error")
446
return flask.render_template("login.html")
447
if username.startswith("-") or username.endswith("-"):
448
flask.flash(Markup(
449
_("Usernames may not start or end with a hyphen")),
450
category="error")
451
return flask.render_template("login.html")
452
if username in config.RESERVED_NAMES:
453
flask.flash(
454
Markup(
455
_("Sorry, {username} is a system path").format(
456
username=username)),
457
category="error")
458
return flask.render_template("login.html")
459
460
if not username.islower():
461
if not name: # infer display name from the wanted username if not customised
462
display_name = username
463
username = username.lower()
464
flask.flash(Markup(
465
_("Usernames must be lowercase, so it's been converted automatically")),
466
category="info")
467
468
user_check = User.query.filter_by(username=username).first()
469
if user_check or email2: # make the honeypot look like a normal error
470
flask.flash(
471
Markup(
472
_(
473
"The username {username} is taken").format(
474
username=username)),
475
category="error")
476
return flask.render_template("login.html")
477
478
if password2 != password:
479
flask.flash(Markup(_(
480
"Make sure the passwords match")),
481
category="error")
482
return flask.render_template("login.html")
483
484
user = User(username, password, email, name)
485
db.session.add(user)
486
db.session.commit()
487
flask.session["username"] = user.username
488
flask.flash(Markup(
489
_(
490
"Successfully created and logged in as {username}").format(
491
username=username)),
492
category="success")
493
494
notification = Notification({"type": "welcome"})
495
db.session.add(notification)
496
db.session.commit()
497
498
return flask.redirect("/", code=303)
499
500
501
@app.route("/newrepo/", methods=["GET", "POST"])
502
def new_repo():
503
if not flask.session.get("username"):
504
flask.abort(401)
505
if flask.request.method == "GET":
506
return flask.render_template("new-repo.html")
507
else:
508
name = flask.request.form["name"]
509
visibility = int(flask.request.form["visibility"])
510
511
if not only_chars(name,
512
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_."):
513
flask.flash(Markup(
514
"<iconify-icon icon='mdi:error'></iconify-icon>" + _(
515
"Repository names may only contain Latin alphabet, numbers, '-', '_' and '.'")),
516
category="error")
517
return flask.render_template("new-repo.html")
518
519
user = User.query.filter_by(username=flask.session.get("username")).first()
520
521
repo = Repo(user, name, visibility)
522
db.session.add(repo)
523
db.session.commit()
524
525
flask.flash(Markup(_("Successfully created repository {name}").format(name=name)),
526
category="success")
527
return flask.redirect(repo.route, code=303)
528
529
530
@app.route("/logout")
531
def logout():
532
flask.session.clear()
533
flask.flash(Markup(
534
"<iconify-icon icon='mdi:account'></iconify-icon>" + _("Successfully logged out")),
535
category="info")
536
return flask.redirect("/", code=303)
537
538
539
@app.route("/<username>/", methods=["GET", "POST"])
540
def user_profile(username):
541
if db.session.get(User, username) is None:
542
flask.abort(404)
543
old_relationship = UserFollow.query.filter_by(
544
follower_username=flask.session.get("username"),
545
followed_username=username).first()
546
if flask.request.method == "GET":
547
user = User.query.filter_by(username=username).first()
548
match flask.request.args.get("action"):
549
case "repositories":
550
repos = Repo.query.filter_by(owner_name=username, visibility=2)
551
return flask.render_template("user-profile-repositories.html", user=user,
552
repos=repos,
553
relationship=old_relationship)
554
case "followers":
555
return flask.render_template("user-profile-followers.html", user=user,
556
relationship=old_relationship)
557
case "follows":
558
return flask.render_template("user-profile-follows.html", user=user,
559
relationship=old_relationship)
560
case _:
561
return flask.render_template("user-profile-overview.html", user=user,
562
relationship=old_relationship)
563
564
elif flask.request.method == "POST":
565
match flask.request.args.get("action"):
566
case "follow":
567
if username == flask.session.get("username"):
568
flask.abort(403)
569
if old_relationship:
570
db.session.delete(old_relationship)
571
else:
572
relationship = UserFollow(
573
flask.session.get("username"),
574
username
575
)
576
print(f"Following {username}")
577
db.session.add(relationship)
578
579
user = db.session.get(User, username)
580
author = db.session.get(User, flask.session.get("username"))
581
notification = Notification({"type": "follow", "author": author.username, "user": user.username})
582
db.session.add(notification)
583
db.session.commit()
584
user_notification = UserNotification(user, notification, 1)
585
db.session.add(user_notification)
586
db.session.commit()
587
588
db.session.commit()
589
return flask.redirect("?", code=303)
590
591
592
@app.route("/<username>/<repository>/")
593
def repository_index(username, repository):
594
return flask.redirect("./tree", code=302)
595
596
597
@app.route("/info/<username>/avatar")
598
def user_avatar(username):
599
server_userdata_location = os.path.join(config.USERDATA_PATH, username)
600
if not os.path.exists(server_userdata_location):
601
return flask.render_template("errors/not-found.html"), 404
602
603
return flask.send_from_directory(server_userdata_location, "avatar.png")
604
605
606
@app.route("/info/<username>/avatar", methods=["POST"])
607
def user_avatar_upload(username):
608
server_userdata_location = os.path.join(config.USERDATA_PATH, username)
609
610
if not os.path.exists(server_userdata_location):
611
flask.abort(404)
612
if not flask.session.get("username") == username:
613
flask.abort(403)
614
615
# Convert image to PNG
616
try:
617
image = Image.open(flask.request.files["avatar"])
618
except PIL.UnidentifiedImageError:
619
flask.abort(400)
620
image.save(os.path.join(server_userdata_location, "avatar.png"))
621
622
return flask.redirect(f"/{username}", code=303)
623
624
625
@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>")
626
def repository_raw(username, repository, branch, subpath):
627
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
628
if not os.path.exists(server_repo_location):
629
flask.abort(404)
630
if not (get_visibility(username, repository) or get_permission_level(
631
flask.session.get("username"), username,
632
repository) is not None):
633
flask.abort(403)
634
635
if not os.path.exists(server_repo_location):
636
return flask.render_template("errors/not-found.html"), 404
637
638
repo = git.Repo(server_repo_location)
639
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
640
if not repo_data.default_branch:
641
if repo.heads:
642
repo_data.default_branch = repo.heads[0].name
643
else:
644
return flask.render_template("empty.html",
645
remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
646
if not branch:
647
branch = repo_data.default_branch
648
return flask.redirect(f"./{branch}", code=302)
649
650
if branch.startswith("tag:"):
651
ref = f"tags/{branch[4:]}"
652
elif branch.startswith("~"):
653
ref = branch[1:]
654
else:
655
ref = f"heads/{branch}"
656
657
ref = ref.replace("~", "/") # encode slashes for URL support
658
659
try:
660
repo.git.checkout("-f", ref)
661
except git.exc.GitCommandError:
662
return flask.render_template("errors/not-found.html"), 404
663
664
return flask.send_from_directory(config.REPOS_PATH,
665
os.path.join(username, repository, subpath))
666
667
668
@repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""})
669
@repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""})
670
@repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>")
671
def repository_tree(username, repository, branch, subpath):
672
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
673
if not os.path.exists(server_repo_location):
674
flask.abort(404)
675
if not (get_visibility(username, repository) or get_permission_level(
676
flask.session.get("username"), username,
677
repository) is not None):
678
flask.abort(403)
679
680
repo = git.Repo(server_repo_location)
681
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
682
if not repo_data.default_branch:
683
if repo.heads:
684
repo_data.default_branch = repo.heads[0].name
685
else:
686
return flask.render_template("empty.html",
687
remote=f"{config.www_protocol}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
688
if not branch:
689
branch = repo_data.default_branch
690
return flask.redirect(f"./{branch}", code=302)
691
692
if branch.startswith("tag:"):
693
ref = f"tags/{branch[4:]}"
694
elif branch.startswith("~"):
695
ref = branch[1:]
696
else:
697
ref = f"heads/{branch}"
698
699
ref = ref.replace("~", "/") # encode slashes for URL support
700
701
try:
702
repo.git.checkout("-f", ref)
703
except git.exc.GitCommandError:
704
return flask.render_template("errors/not-found.html"), 404
705
706
branches = repo.heads
707
708
all_refs = []
709
for ref in repo.heads:
710
all_refs.append((ref, "head"))
711
for ref in repo.tags:
712
all_refs.append((ref, "tag"))
713
714
if os.path.isdir(os.path.join(server_repo_location, subpath)):
715
files = []
716
blobs = []
717
718
for entry in os.listdir(os.path.join(server_repo_location, subpath)):
719
if not os.path.basename(entry) == ".git":
720
files.append(os.path.join(subpath, entry))
721
722
infos = []
723
724
for file in files:
725
path = os.path.join(server_repo_location, file)
726
mimetype = guess_mime(path)
727
728
text = git_command(server_repo_location, None, "log", "--format='%H\n'",
729
shlex.quote(file)).decode()
730
731
sha = text.split("\n")[0]
732
identifier = f"/{username}/{repository}/{sha}"
733
734
last_commit = db.session.get(Commit, identifier)
735
736
info = {
737
"name": os.path.basename(file),
738
"serverPath": path,
739
"relativePath": file,
740
"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file),
741
"size": human_size(os.path.getsize(path)),
742
"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}",
743
"commit": last_commit,
744
"shaSize": 7,
745
}
746
747
special_icon = config.match_icon(os.path.basename(file))
748
if special_icon:
749
info["icon"] = special_icon
750
elif os.path.isdir(path):
751
info["icon"] = config.folder_icon
752
info["size"] = _("{} files").format(len(os.listdir(path)))
753
elif mimetypes.guess_type(path)[0] in config.file_icons:
754
info["icon"] = config.file_icons[mimetypes.guess_type(path)[0]]
755
else:
756
info["icon"] = config.unknown_icon
757
758
if os.path.isdir(path):
759
infos.insert(0, info)
760
else:
761
infos.append(info)
762
763
return flask.render_template(
764
"repo-tree.html",
765
username=username,
766
repository=repository,
767
files=infos,
768
subpath=os.path.join("/", subpath),
769
branches=all_refs,
770
current=branch,
771
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
772
is_favourite=get_favourite(flask.session.get("username"), username, repository),
773
repo_data=repo_data,
774
)
775
else:
776
path = os.path.join(server_repo_location, subpath)
777
778
if not os.path.exists(path):
779
return flask.render_template("errors/not-found.html"), 404
780
781
mimetype = guess_mime(path)
782
mode = mimetype.split("/", 1)[0]
783
size = human_size(os.path.getsize(path))
784
785
special_icon = config.match_icon(os.path.basename(path))
786
if special_icon:
787
icon = special_icon
788
elif os.path.isdir(path):
789
icon = config.folder_icon
790
elif mimetypes.guess_type(path)[0] in config.file_icons:
791
icon = config.file_icons[mimetypes.guess_type(path)[0]]
792
else:
793
icon = config.unknown_icon
794
795
contents = None
796
if mode == "text":
797
contents = convert_to_html(path)
798
799
return flask.render_template(
800
"repo-file.html",
801
username=username,
802
repository=repository,
803
file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath),
804
branches=all_refs,
805
current=branch,
806
mode=mode,
807
mimetype=mimetype,
808
detailedtype=magic.from_file(path),
809
size=size,
810
icon=icon,
811
subpath=os.path.join("/", subpath),
812
extension=pathlib.Path(path).suffix,
813
basename=os.path.basename(path),
814
contents=contents,
815
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
816
is_favourite=get_favourite(flask.session.get("username"), username, repository),
817
repo_data=repo_data,
818
)
819
820
821
@repositories.route("/<username>/<repository>/commit/<sha>")
822
def repository_commit(username, repository, sha):
823
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
824
if not os.path.exists(server_repo_location):
825
flask.abort(404)
826
if not (get_visibility(username, repository) or get_permission_level(
827
flask.session.get("username"), username,
828
repository) is not None):
829
flask.abort(403)
830
831
if not os.path.exists(server_repo_location):
832
return flask.render_template("errors/not-found.html"), 404
833
834
repo = git.Repo(server_repo_location)
835
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
836
837
files = git_command(os.path.join(server_repo_location, ".git"), None, "diff-tree", "-r",
838
"--name-only", "--no-commit-id", sha).decode().split("\n")[:-1]
839
840
return flask.render_template(
841
"repo-commit.html",
842
username=username,
843
repository=repository,
844
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
845
is_favourite=get_favourite(flask.session.get("username"), username, repository),
846
diff={file: git_command(os.path.join(server_repo_location, ".git"), None, "diff",
847
str(sha) + "^!", "--", file).decode().split("\n") for
848
file in files},
849
data=db.session.get(Commit, f"/{username}/{repository}/{sha}"),
850
repo_data=repo_data,
851
comment_query=Comment.query,
852
permission_level=get_permission_level(flask.session.get("username"), username, repository),
853
)
854
855
856
@repositories.route("/<username>/<repository>/commit/<sha>/add_comment", methods=["POST"])
857
def repository_commit_add_comment(username, repository, sha):
858
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
859
if not os.path.exists(server_repo_location):
860
flask.abort(404)
861
if not (get_visibility(username, repository) or get_permission_level(
862
flask.session.get("username"), username,
863
repository) is not None):
864
flask.abort(403)
865
866
comment = Comment(
867
db.session.get(User, flask.session.get("username")),
868
db.session.get(Repo, f"/{username}/{repository}"),
869
db.session.get(Commit, f"/{username}/{repository}/{sha}"),
870
flask.request.form["comment"],
871
flask.request.form["file"],
872
flask.request.form["line"],
873
)
874
875
db.session.add(comment)
876
db.session.commit()
877
878
return flask.redirect(
879
flask.url_for(".repository_commit", username=username, repository=repository, sha=sha),
880
code=303
881
)
882
883
884
@repositories.route("/<username>/<repository>/commit/<sha>/delete_comment/<int:id>", methods=["POST"])
885
def repository_commit_delete_comment(username, repository, sha, id):
886
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
887
comment = Comment.query.filter_by(identifier=f"/{username}/{repository}/{id}").first()
888
commit = Commit.query.filter_by(identifier=f"/{username}/{repository}/{sha}").first()
889
if (
890
comment.owner.username == flask.session.get("username")
891
or get_permission_level(flask.session.get("username"), username, repository) >= 2
892
or comment.commit.owner.username == flask.session.get("username")
893
):
894
db.session.delete(comment)
895
db.session.commit()
896
897
return flask.redirect(
898
flask.url_for(".repository_commit", username=username, repository=repository, sha=sha),
899
code=303
900
)
901
902
903
@repositories.route("/<username>/<repository>/commit/<sha>/resolve_comment/<int:id>", methods=["POST"])
904
def repository_commit_resolve_comment(username, repository, sha, id):
905
comment = Comment.query.filter_by(identifier=f"/{username}/{repository}/{id}").first()
906
if (
907
comment.commit.owner.username == flask.session.get("username")
908
or get_permission_level(flask.session.get("username"), username, repository) >= 2
909
or comment.owner.username == flask.session.get("username")
910
):
911
comment.state = int(not comment.state)
912
db.session.commit()
913
914
return flask.redirect(
915
flask.url_for(".repository_commit", username=username, repository=repository, sha=sha),
916
code=303
917
)
918
919
920
@repositories.route("/<username>/<repository>/forum/")
921
def repository_forum(username, repository):
922
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
923
if not os.path.exists(server_repo_location):
924
flask.abort(404)
925
if not (get_visibility(username, repository) or get_permission_level(
926
flask.session.get("username"), username,
927
repository) is not None):
928
flask.abort(403)
929
930
repo = git.Repo(server_repo_location)
931
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
932
user = User.query.filter_by(username=flask.session.get("username")).first()
933
relationships = RepoAccess.query.filter_by(repo=repo_data)
934
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
935
936
return flask.render_template(
937
"repo-forum.html",
938
username=username,
939
repository=repository,
940
repo_data=repo_data,
941
relationships=relationships,
942
repo=repo,
943
user_relationship=user_relationship,
944
Post=Post,
945
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
946
is_favourite=get_favourite(flask.session.get("username"), username, repository),
947
default_branch=repo_data.default_branch
948
)
949
950
951
@repositories.route("/<username>/<repository>/forum/search")
952
def repository_forum_search(username, repository):
953
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
954
if not os.path.exists(server_repo_location):
955
flask.abort(404)
956
if not (get_visibility(username, repository) or get_permission_level(
957
flask.session.get("username"), username,
958
repository) is not None):
959
flask.abort(403)
960
961
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
962
user = User.query.filter_by(username=flask.session.get("username")).first()
963
relationships = RepoAccess.query.filter_by(repo=repo_data)
964
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
965
966
query = flask.request.args.get("q")
967
968
page_number = flask.request.args.get("page", 1, type=int)
969
if flask.session.get("username"):
970
default_page_length = db.session.get(User, flask.session.get("username")).default_page_length
971
else:
972
default_page_length = 16
973
974
page_length = flask.request.args.get("per_page", default_page_length, type=int)
975
976
results = Post.query.filter(Post.repo == repo_data).filter(Post.subject.ilike(f"%{query}%") | Post.message.ilike(f"%{query}%")).order_by(Post.last_updated.desc()).paginate(page=page_number, per_page=page_length)
977
978
if results.has_next:
979
next_page = results.next_num
980
else:
981
next_page = None
982
983
if results.has_prev:
984
prev_page = results.prev_num
985
else:
986
prev_page = None
987
988
return flask.render_template(
989
"repo-forum-search.html",
990
username=username,
991
repository=repository,
992
repo_data=repo_data,
993
relationships=relationships,
994
user_relationship=user_relationship,
995
query=query,
996
results=results,
997
Post=Post,
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
default_branch=repo_data.default_branch,
1001
page_number=page_number,
1002
page_length=page_length,
1003
next_page=next_page,
1004
prev_page=prev_page,
1005
num_pages=results.pages
1006
)
1007
1008
1009
@repositories.route("/<username>/<repository>/forum/topic/<int:id>")
1010
def repository_forum_topic(username, repository, id):
1011
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1012
if not os.path.exists(server_repo_location):
1013
flask.abort(404)
1014
if not (get_visibility(username, repository) or get_permission_level(
1015
flask.session.get("username"), username,
1016
repository) is not None):
1017
flask.abort(403)
1018
1019
if not os.path.exists(server_repo_location):
1020
return flask.render_template("errors/not-found.html"), 404
1021
1022
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1023
user = User.query.filter_by(username=flask.session.get("username")).first()
1024
relationships = RepoAccess.query.filter_by(repo=repo_data)
1025
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1026
1027
post = Post.query.filter_by(id=id).first()
1028
1029
return flask.render_template(
1030
"repo-topic.html",
1031
username=username,
1032
repository=repository,
1033
repo_data=repo_data,
1034
relationships=relationships,
1035
user_relationship=user_relationship,
1036
post=post,
1037
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1038
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1039
default_branch=repo_data.default_branch
1040
)
1041
1042
1043
@repositories.route("/<username>/<repository>/forum/new", methods=["POST", "GET"])
1044
def repository_forum_new(username, repository):
1045
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1046
if not os.path.exists(server_repo_location):
1047
flask.abort(404)
1048
if not ((flask.session.get("username") and get_visibility(username, repository)) or get_permission_level(
1049
flask.session.get("username"), username,
1050
repository) is not None):
1051
flask.abort(403)
1052
1053
if not os.path.exists(server_repo_location):
1054
return flask.render_template("errors/not-found.html"), 404
1055
1056
repo = git.Repo(server_repo_location)
1057
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1058
user = User.query.filter_by(username=flask.session.get("username")).first()
1059
relationships = RepoAccess.query.filter_by(repo=repo_data)
1060
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1061
1062
post = Post(user, repo_data, None, flask.request.form["subject"],
1063
flask.request.form["message"])
1064
1065
db.session.add(post)
1066
db.session.commit()
1067
1068
return flask.redirect(
1069
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1070
post_id=post.number),
1071
code=303)
1072
1073
1074
@repositories.route("/<username>/<repository>/forum/<int:post_id>")
1075
def repository_forum_thread(username, repository, post_id):
1076
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1077
if not os.path.exists(server_repo_location):
1078
flask.abort(404)
1079
if not (get_visibility(username, repository) or get_permission_level(
1080
flask.session.get("username"), username,
1081
repository) is not None):
1082
flask.abort(403)
1083
1084
if not os.path.exists(server_repo_location):
1085
return flask.render_template("errors/not-found.html"), 404
1086
1087
repo = git.Repo(server_repo_location)
1088
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1089
user = User.query.filter_by(username=flask.session.get("username")).first()
1090
relationships = RepoAccess.query.filter_by(repo=repo_data)
1091
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1092
1093
if user:
1094
max_post_nesting = user.max_post_nesting
1095
else:
1096
max_post_nesting = 2
1097
1098
return flask.render_template(
1099
"repo-forum-thread.html",
1100
username=username,
1101
repository=repository,
1102
repo_data=repo_data,
1103
relationships=relationships,
1104
repo=repo,
1105
Post=Post,
1106
user_relationship=user_relationship,
1107
post_id=post_id,
1108
max_post_nesting=max_post_nesting,
1109
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1110
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1111
parent=Post.query.filter_by(repo=repo_data, number=post_id).first(),
1112
has_permission=not ((not get_permission_level(flask.session.get("username"), username,
1113
repository)) and db.session.get(Post,
1114
f"/{username}/{repository}/{post_id}").owner.username != flask.session.get("username")),
1115
)
1116
1117
1118
@repositories.route("/<username>/<repository>/forum/<int:post_id>/change-state",
1119
methods=["POST"])
1120
def repository_forum_change_state(username, repository, post_id):
1121
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1122
if not os.path.exists(server_repo_location):
1123
flask.abort(404)
1124
if (not get_permission_level(flask.session.get("username"), username, repository)) and db.session.get(Post, f"/{username}/{repository}/{post_id}").owner.username != flask.session.get("username"):
1125
flask.abort(403)
1126
1127
repo = git.Repo(server_repo_location)
1128
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1129
user = User.query.filter_by(username=flask.session.get("username")).first()
1130
relationships = RepoAccess.query.filter_by(repo=repo_data)
1131
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1132
1133
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1134
1135
if not post:
1136
flask.abort(404)
1137
if post.parent:
1138
flask.abort(400)
1139
1140
post.state = int(flask.request.form["new-state"])
1141
1142
db.session.commit()
1143
1144
return flask.redirect(
1145
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1146
post_id=post_id),
1147
code=303)
1148
1149
1150
@repositories.route("/<username>/<repository>/forum/<int:post_id>/reply", methods=["POST"])
1151
def repository_forum_reply(username, repository, post_id):
1152
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1153
if not os.path.exists(server_repo_location):
1154
flask.abort(404)
1155
if not ((flask.session.get("username") and get_visibility(username, repository)) or get_permission_level(
1156
flask.session.get("username"), username,
1157
repository) is not None):
1158
flask.abort(403)
1159
1160
if not os.path.exists(server_repo_location):
1161
return flask.render_template("errors/not-found.html"), 404
1162
1163
repo = git.Repo(server_repo_location)
1164
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1165
user = User.query.filter_by(username=flask.session.get("username")).first()
1166
relationships = RepoAccess.query.filter_by(repo=repo_data)
1167
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1168
if not user:
1169
flask.abort(401)
1170
1171
parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1172
post = Post(user, repo_data, parent, flask.request.form["subject"],
1173
flask.request.form["message"])
1174
1175
db.session.add(post)
1176
post.update_date()
1177
db.session.commit()
1178
1179
return flask.redirect(
1180
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1181
post_id=post_id),
1182
code=303)
1183
1184
1185
@repositories.route("/<username>/<repository>/forum/<int:post_id>/edit", methods=["POST"])
1186
def repository_forum_edit(username, repository, post_id):
1187
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1188
if not os.path.exists(server_repo_location):
1189
flask.abort(404)
1190
if not (get_visibility(username, repository) or get_permission_level(
1191
flask.session.get("username"), username,
1192
repository) is not None):
1193
flask.abort(403)
1194
1195
if not os.path.exists(server_repo_location):
1196
return flask.render_template("errors/not-found.html"), 404
1197
1198
repo = git.Repo(server_repo_location)
1199
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1200
user = User.query.filter_by(username=flask.session.get("username")).first()
1201
relationships = RepoAccess.query.filter_by(repo=repo_data)
1202
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1203
if not user:
1204
flask.abort(401)
1205
post = db.session.get(Post, f"/{username}/{repository}/{post_id}")
1206
if user != post.owner:
1207
flask.abort(403)
1208
1209
post.subject = flask.request.form["subject"]
1210
post.message = flask.request.form["message"]
1211
post.html = markdown.markdown2html(post.message).prettify()
1212
post.update_date()
1213
db.session.commit()
1214
1215
return flask.redirect(
1216
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1217
post_id=post_id),
1218
code=303)
1219
1220
1221
@repositories.route("/<username>/<repository>/forum/<int:post_id>/edit", methods=["GET"])
1222
def repository_forum_edit_form(username, repository, post_id):
1223
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1224
if not os.path.exists(server_repo_location):
1225
flask.abort(404)
1226
if not (get_visibility(username, repository) or get_permission_level(
1227
flask.session.get("username"), username,
1228
repository) is not None):
1229
flask.abort(403)
1230
1231
if not os.path.exists(server_repo_location):
1232
return flask.render_template("errors/not-found.html"), 404
1233
1234
repo = git.Repo(server_repo_location)
1235
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1236
user = User.query.filter_by(username=flask.session.get("username")).first()
1237
relationships = RepoAccess.query.filter_by(repo=repo_data)
1238
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1239
if not user:
1240
flask.abort(401)
1241
post = db.session.get(Post, f"/{username}/{repository}/{post_id}")
1242
if user != post.owner:
1243
flask.abort(403)
1244
1245
return flask.render_template(
1246
"repo-forum-edit.html",
1247
username=username,
1248
repository=repository,
1249
repo_data=repo_data,
1250
relationships=relationships,
1251
repo=repo,
1252
user_relationship=user_relationship,
1253
post=post,
1254
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1255
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1256
default_branch=repo_data.default_branch
1257
)
1258
1259
@repositories.route("/<username>/<repository>/forum/<int:post_id>/voteup",
1260
defaults={"score": 1})
1261
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votedown",
1262
defaults={"score": -1})
1263
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votes", defaults={"score": 0})
1264
def repository_forum_vote(username, repository, post_id, score):
1265
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1266
if not os.path.exists(server_repo_location):
1267
flask.abort(404)
1268
if not (get_visibility(username, repository) or get_permission_level(
1269
flask.session.get("username"), username,
1270
repository) is not None):
1271
flask.abort(403)
1272
1273
if not os.path.exists(server_repo_location):
1274
return flask.render_template("errors/not-found.html"), 404
1275
1276
repo = git.Repo(server_repo_location)
1277
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1278
user = User.query.filter_by(username=flask.session.get("username")).first()
1279
relationships = RepoAccess.query.filter_by(repo=repo_data)
1280
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1281
if not user:
1282
flask.abort(401)
1283
1284
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1285
1286
if score:
1287
old_relationship = PostVote.query.filter_by(user_username=user.username,
1288
post_identifier=post.identifier).first()
1289
if old_relationship:
1290
if score == old_relationship.vote_score:
1291
db.session.delete(old_relationship)
1292
post.vote_sum -= old_relationship.vote_score
1293
else:
1294
post.vote_sum -= old_relationship.vote_score
1295
post.vote_sum += score
1296
old_relationship.vote_score = score
1297
else:
1298
relationship = PostVote(user, post, score)
1299
post.vote_sum += score
1300
db.session.add(relationship)
1301
1302
db.session.commit()
1303
1304
user_vote = PostVote.query.filter_by(user_username=user.username,
1305
post_identifier=post.identifier).first()
1306
response = flask.make_response(
1307
str(post.vote_sum) + " " + str(user_vote.vote_score if user_vote else 0))
1308
response.content_type = "text/plain"
1309
1310
return response
1311
1312
1313
@repositories.route("/<username>/<repository>/forum/<int:post_id>/label", methods=["POST"])
1314
def repository_forum_label(username, repository, post_id):
1315
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1316
if not os.path.exists(server_repo_location):
1317
flask.abort(404)
1318
if not get_permission_level(flask.session.get("username"), username, repository):
1319
flask.abort(403)
1320
1321
repo = git.Repo(server_repo_location)
1322
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1323
user = User.query.filter_by(username=flask.session.get("username")).first()
1324
relationships = RepoAccess.query.filter_by(repo=repo_data)
1325
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1326
1327
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1328
1329
if not post:
1330
flask.abort(404)
1331
if post.parent:
1332
flask.abort(400)
1333
1334
label = db.session.get(Label, flask.request.form["label"])
1335
1336
if PostLabel.query.filter_by(post=post, label=label).first():
1337
return flask.redirect(
1338
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1339
post_id=post_id),
1340
code=303)
1341
1342
post_label = PostLabel(post, label)
1343
db.session.add(post_label)
1344
1345
db.session.commit()
1346
1347
return flask.redirect(
1348
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1349
post_id=post_id),
1350
code=303)
1351
1352
1353
@repositories.route("/<username>/<repository>/forum/<int:post_id>/remove-label")
1354
def repository_forum_remove_label(username, repository, post_id):
1355
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1356
if not os.path.exists(server_repo_location):
1357
flask.abort(404)
1358
if not get_permission_level(flask.session.get("username"), username, repository):
1359
flask.abort(403)
1360
1361
repo = git.Repo(server_repo_location)
1362
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1363
user = User.query.filter_by(username=flask.session.get("username")).first()
1364
relationships = RepoAccess.query.filter_by(repo=repo_data)
1365
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1366
1367
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1368
1369
if not post:
1370
flask.abort(404)
1371
if post.parent:
1372
flask.abort(400)
1373
1374
label = db.session.get(Label, flask.request.args["label"])
1375
1376
post_label = PostLabel.query.filter_by(post=post, label=label).first()
1377
db.session.delete(post_label)
1378
1379
db.session.commit()
1380
1381
return flask.redirect(
1382
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1383
post_id=post_id),
1384
code=303)
1385
1386
1387
@repositories.route("/<username>/<repository>/favourite")
1388
def repository_favourite(username, repository):
1389
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1390
if not os.path.exists(server_repo_location):
1391
flask.abort(404)
1392
if not (get_visibility(username, repository) or get_permission_level(
1393
flask.session.get("username"), username,
1394
repository) is not None):
1395
flask.abort(403)
1396
1397
if not os.path.exists(server_repo_location):
1398
return flask.render_template("errors/not-found.html"), 404
1399
1400
repo = git.Repo(server_repo_location)
1401
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1402
user = User.query.filter_by(username=flask.session.get("username")).first()
1403
relationships = RepoAccess.query.filter_by(repo=repo_data)
1404
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1405
if not user:
1406
flask.abort(401)
1407
1408
old_relationship = RepoFavourite.query.filter_by(user_username=user.username,
1409
repo_route=repo_data.route).first()
1410
if old_relationship:
1411
db.session.delete(old_relationship)
1412
else:
1413
relationship = RepoFavourite(user, repo_data)
1414
db.session.add(relationship)
1415
1416
db.session.commit()
1417
1418
return flask.redirect(flask.url_for("favourites"), code=303)
1419
1420
1421
@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"])
1422
def repository_users(username, repository):
1423
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1424
if not os.path.exists(server_repo_location):
1425
flask.abort(404)
1426
if not (get_visibility(username, repository) or get_permission_level(
1427
flask.session.get("username"), username,
1428
repository) is not None):
1429
flask.abort(403)
1430
1431
if not os.path.exists(server_repo_location):
1432
return flask.render_template("errors/not-found.html"), 404
1433
1434
repo = git.Repo(server_repo_location)
1435
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1436
user = User.query.filter_by(username=flask.session.get("username")).first()
1437
relationships = RepoAccess.query.filter_by(repo=repo_data)
1438
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1439
1440
if flask.request.method == "GET":
1441
return flask.render_template(
1442
"repo-users.html",
1443
username=username,
1444
repository=repository,
1445
repo_data=repo_data,
1446
relationships=relationships,
1447
repo=repo,
1448
user_relationship=user_relationship,
1449
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1450
is_favourite=get_favourite(flask.session.get("username"), username, repository)
1451
)
1452
else:
1453
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1454
flask.abort(401)
1455
1456
if flask.request.form.get("new-username"):
1457
# Create new relationship
1458
new_user = User.query.filter_by(
1459
username=flask.request.form.get("new-username")).first()
1460
relationship = RepoAccess(new_user, repo_data, flask.request.form.get("new-level"))
1461
db.session.add(relationship)
1462
db.session.commit()
1463
if flask.request.form.get("update-username"):
1464
# Create new relationship
1465
updated_user = User.query.filter_by(
1466
username=flask.request.form.get("update-username")).first()
1467
relationship = RepoAccess.query.filter_by(repo=repo_data, user=updated_user).first()
1468
if flask.request.form.get("update-level") == -1:
1469
relationship.delete()
1470
else:
1471
relationship.access_level = flask.request.form.get("update-level")
1472
db.session.commit()
1473
1474
return flask.redirect(
1475
app.url_for(".repository_users", username=username, repository=repository))
1476
1477
1478
@repositories.route("/<username>/<repository>/branches/")
1479
def repository_branches(username, repository):
1480
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1481
if not os.path.exists(server_repo_location):
1482
flask.abort(404)
1483
if not (get_visibility(username, repository) or get_permission_level(
1484
flask.session.get("username"), username,
1485
repository) is not None):
1486
flask.abort(403)
1487
1488
if not os.path.exists(server_repo_location):
1489
return flask.render_template("errors/not-found.html"), 404
1490
1491
repo = git.Repo(server_repo_location)
1492
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1493
1494
return flask.render_template(
1495
"repo-branches.html",
1496
username=username,
1497
repository=repository,
1498
repo_data=repo_data,
1499
repo=repo,
1500
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1501
is_favourite=get_favourite(flask.session.get("username"), username, repository)
1502
)
1503
1504
1505
@repositories.route("/<username>/<repository>/log/", defaults={"branch": None})
1506
@repositories.route("/<username>/<repository>/log/<branch>/")
1507
def repository_log(username, repository, branch):
1508
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1509
if not os.path.exists(server_repo_location):
1510
flask.abort(404)
1511
if not (get_visibility(username, repository) or get_permission_level(
1512
flask.session.get("username"), username,
1513
repository) is not None):
1514
flask.abort(403)
1515
1516
if not os.path.exists(server_repo_location):
1517
return flask.render_template("errors/not-found.html"), 404
1518
1519
repo = git.Repo(server_repo_location)
1520
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1521
if not repo_data.default_branch:
1522
if repo.heads:
1523
repo_data.default_branch = repo.heads[0].name
1524
else:
1525
return flask.render_template("empty.html",
1526
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
1527
if not branch:
1528
branch = repo_data.default_branch
1529
return flask.redirect(f"./{branch}", code=302)
1530
1531
if branch.startswith("tag:"):
1532
ref = f"tags/{branch[4:]}"
1533
elif branch.startswith("~"):
1534
ref = branch[1:]
1535
else:
1536
ref = f"heads/{branch}"
1537
1538
ref = ref.replace("~", "/") # encode slashes for URL support
1539
1540
try:
1541
repo.git.checkout("-f", ref)
1542
except git.exc.GitCommandError:
1543
return flask.render_template("errors/not-found.html"), 404
1544
1545
branches = repo.heads
1546
1547
all_refs = []
1548
for ref in repo.heads:
1549
all_refs.append((ref, "head"))
1550
for ref in repo.tags:
1551
all_refs.append((ref, "tag"))
1552
1553
commit_list = [f"/{username}/{repository}/{sha}" for sha in
1554
git_command(server_repo_location, None, "log",
1555
"--format='%H'").decode().split("\n")]
1556
1557
commits = Commit.query.filter(Commit.identifier.in_(commit_list)).order_by(Commit.author_date.desc())
1558
page_number = flask.request.args.get("page", 1, type=int)
1559
if flask.session.get("username"):
1560
default_page_length = db.session.get(User, flask.session.get("username")).default_page_length
1561
else:
1562
default_page_length = 16
1563
page_length = flask.request.args.get("per_page", default_page_length, type=int)
1564
page_listing = db.paginate(commits, page=page_number, per_page=page_length)
1565
1566
if page_listing.has_next:
1567
next_page = page_listing.next_num
1568
else:
1569
next_page = None
1570
1571
if page_listing.has_prev:
1572
prev_page = page_listing.prev_num
1573
else:
1574
prev_page = None
1575
1576
return flask.render_template(
1577
"repo-log.html",
1578
username=username,
1579
repository=repository,
1580
branches=all_refs,
1581
current=branch,
1582
repo_data=repo_data,
1583
repo=repo,
1584
commits=page_listing,
1585
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1586
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1587
page_number=page_number,
1588
page_length=page_length,
1589
next_page=next_page,
1590
prev_page=prev_page,
1591
num_pages=page_listing.pages
1592
)
1593
1594
1595
@repositories.route("/<username>/<repository>/prs/", methods=["GET", "POST"])
1596
def repository_prs(username, repository):
1597
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1598
if not os.path.exists(server_repo_location):
1599
flask.abort(404)
1600
if not (get_visibility(username, repository) or get_permission_level(
1601
flask.session.get("username"), username,
1602
repository) is not None):
1603
flask.abort(403)
1604
1605
if not os.path.exists(server_repo_location):
1606
return flask.render_template("errors/not-found.html"), 404
1607
1608
if flask.request.method == "GET":
1609
repo = git.Repo(server_repo_location)
1610
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1611
user = User.query.filter_by(username=flask.session.get("username")).first()
1612
1613
return flask.render_template(
1614
"repo-prs.html",
1615
username=username,
1616
repository=repository,
1617
repo_data=repo_data,
1618
repo=repo,
1619
PullRequest=PullRequest,
1620
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1621
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1622
default_branch=repo_data.default_branch,
1623
branches=repo.branches
1624
)
1625
1626
elif "id" not in flask.request.form:
1627
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1628
head = flask.request.form.get("head")
1629
head_route = flask.request.form.get("headroute")
1630
base = flask.request.form.get("base")
1631
1632
if not head and base and head_route:
1633
return flask.redirect(".", 400)
1634
1635
head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/")))
1636
base_repo = git.Repo(server_repo_location)
1637
1638
if head not in head_repo.branches or base not in base_repo.branches:
1639
flask.flash(Markup(
1640
"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Bad branch name")),
1641
category="error")
1642
return flask.redirect(".", 303)
1643
1644
head_data = db.session.get(Repo, head_route)
1645
if not head_data.visibility:
1646
flask.flash(Markup(
1647
"<iconify-icon icon='mdi:error'></iconify-icon>" + _(
1648
"Head can't be restricted")),
1649
category="error")
1650
return flask.redirect(".", 303)
1651
1652
pull_request = PullRequest(head_data, head, repo_data, base,
1653
db.session.get(User, flask.session["username"]))
1654
1655
db.session.add(pull_request)
1656
db.session.commit()
1657
1658
# Create the notification
1659
notification = Notification({"type": "pr", "head": pull_request.head.route, "base": pull_request.base.route, "pr": pull_request.id})
1660
db.session.add(notification)
1661
db.session.commit()
1662
1663
# Send a notification to all users who have enabled PR notifications for this repo
1664
for relationship in RepoFavourite.query.filter_by(repo_route=pull_request.base.route, notify_pr=True).all():
1665
user = relationship.user
1666
user_notification = UserNotification(user, notification, 1)
1667
db.session.add(user_notification)
1668
db.session.commit()
1669
celery_tasks.send_notification.apply_async(args=[user_notification.id])
1670
1671
return flask.redirect(".", 303)
1672
else:
1673
id = flask.request.form.get("id")
1674
pull_request = db.session.get(PullRequest, id)
1675
1676
if not pull_request:
1677
flask.abort(404)
1678
1679
if not (get_visibility(username, repository) or get_permission_level(
1680
flask.session.get("username"), username,
1681
repository) >= 1 or pull_request.owner.username == flask.session.get("username")):
1682
flask.abort(403)
1683
1684
if not get_permission_level(flask.session.get("username"), username, repository):
1685
flask.abort(401)
1686
1687
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1688
1689
if pull_request:
1690
pull_request.resolves_list = flask.request.form.get("resolves")
1691
db.session.commit()
1692
1693
return flask.redirect(".", 303)
1694
1695
1696
@repositories.route("/<username>/<repository>/prs/merge", methods=["POST"])
1697
def repository_prs_merge(username, repository):
1698
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1699
if not os.path.exists(server_repo_location):
1700
flask.abort(404)
1701
if not (get_visibility(username, repository) or get_permission_level(
1702
flask.session.get("username"), username,
1703
repository) is not None):
1704
flask.abort(403)
1705
1706
if not get_permission_level(flask.session.get("username"), username, repository):
1707
flask.abort(401)
1708
1709
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1710
repo = git.Repo(server_repo_location)
1711
id = flask.request.form.get("id")
1712
1713
pull_request = db.session.get(PullRequest, id)
1714
1715
if pull_request:
1716
result = celery_tasks.merge_heads.delay(
1717
pull_request.head_route,
1718
pull_request.head_branch,
1719
pull_request.base_route,
1720
pull_request.base_branch,
1721
pull_request.id,
1722
simulate=True
1723
)
1724
task_result = worker.AsyncResult(result.id)
1725
1726
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303) # should be 202 Accepted but we must use a redirect
1727
# db.session.delete(pull_request)
1728
# db.session.commit()
1729
else:
1730
flask.abort(400)
1731
1732
1733
@repositories.route("/<username>/<repository>/prs/<int:id>/merge")
1734
def repository_prs_merge_stage_two(username, repository, id):
1735
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1736
if not os.path.exists(server_repo_location):
1737
flask.abort(404)
1738
if not (get_visibility(username, repository) or get_permission_level(
1739
flask.session.get("username"), username,
1740
repository) is not None):
1741
flask.abort(403)
1742
1743
if not get_permission_level(flask.session.get("username"), username, repository):
1744
flask.abort(401)
1745
1746
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1747
repo = git.Repo(server_repo_location)
1748
1749
pull_request = db.session.get(PullRequest, id)
1750
1751
if pull_request:
1752
result = celery_tasks.merge_heads.delay(
1753
pull_request.head_route,
1754
pull_request.head_branch,
1755
pull_request.base_route,
1756
pull_request.base_branch,
1757
pull_request.id,
1758
simulate=False
1759
)
1760
task_result = worker.AsyncResult(result.id)
1761
1762
db.session.commit()
1763
1764
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
1765
# db.session.delete(pull_request)
1766
else:
1767
flask.abort(400)
1768
1769
1770
@app.route("/task/<task_id>")
1771
def task_monitor(task_id):
1772
task_result = worker.AsyncResult(task_id)
1773
1774
if flask.request.args.get("partial"):
1775
# htmx partial update
1776
return render_block("task-monitor.html", "content", result=task_result, query_string=flask.request.query_string.decode(), delay=1000)
1777
1778
# Since most tasks finish rather quickly, the initial delay is faster, so it doesn't wait for too long
1779
return flask.render_template("task-monitor.html", result=task_result, query_string=flask.request.query_string.decode(), delay=125)
1780
1781
1782
@repositories.route("/<username>/<repository>/prs/delete", methods=["POST"])
1783
def repository_prs_delete(username, repository):
1784
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1785
if not os.path.exists(server_repo_location):
1786
flask.abort(404)
1787
if not (get_visibility(username, repository) or get_permission_level(
1788
flask.session.get("username"), username,
1789
repository) is not None):
1790
flask.abort(403)
1791
1792
if not get_permission_level(flask.session.get("username"), username, repository):
1793
flask.abort(401)
1794
1795
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1796
repo = git.Repo(server_repo_location)
1797
id = flask.request.form.get("id")
1798
1799
pull_request = db.session.get(PullRequest, id)
1800
1801
if pull_request:
1802
pull_request.state = 2
1803
db.session.commit()
1804
1805
return flask.redirect(".", 303)
1806
1807
1808
@repositories.route("/<username>/<repository>/settings/")
1809
def repository_settings(username, repository):
1810
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1811
flask.abort(401)
1812
1813
repo = git.Repo(os.path.join(config.REPOS_PATH, username, repository))
1814
1815
site_link = Markup(f"<code>http{'s' if config.suggest_https else ''}://{username}.{config.BASE_DOMAIN}/{repository}</code>")
1816
primary_site_link = Markup(f"<code>http{'s' if config.suggest_https else ''}://{username}.{config.BASE_DOMAIN}/</code>")
1817
1818
return flask.render_template("repo-settings.html", username=username, repository=repository,
1819
repo_data=db.session.get(Repo, f"/{username}/{repository}"),
1820
branches=[branch.name for branch in repo.branches],
1821
site_link=site_link, primary_site_link=primary_site_link,
1822
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1823
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1824
)
1825
1826
1827
@repositories.route("/<username>/<repository>/settings/", methods=["POST"])
1828
def repository_settings_post(username, repository):
1829
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1830
flask.abort(401)
1831
1832
repo = db.session.get(Repo, f"/{username}/{repository}")
1833
1834
repo.visibility = flask.request.form.get("visibility", type=int)
1835
repo.info = flask.request.form.get("description")
1836
repo.default_branch = flask.request.form.get("default_branch")
1837
repo.url = flask.request.form.get("url")
1838
1839
# Update site settings
1840
had_site = repo.has_site
1841
old_branch = repo.site_branch
1842
if flask.request.form.get("site_branch"):
1843
repo.site_branch = flask.request.form.get("site_branch")
1844
if flask.request.form.get("primary_site"):
1845
if had_site != 2:
1846
# Remove primary site from other repos
1847
for other_repo in Repo.query.filter_by(owner=repo.owner, has_site=2):
1848
other_repo.has_site = 1 # switch it to a regular site
1849
flask.flash(Markup(
1850
_("Your repository {repository} has been demoted from a primary site to a regular site because there can only be one primary site per user.").format(
1851
repository=other_repo.route
1852
)), category="warning")
1853
repo.has_site = 2
1854
else:
1855
repo.has_site = 1
1856
else:
1857
repo.site_branch = None
1858
repo.has_site = 0
1859
1860
db.session.commit()
1861
1862
if not (had_site, old_branch) == (repo.has_site, repo.site_branch):
1863
# Deploy the newly activated site
1864
result = celery_tasks.copy_site.delay(repo.route)
1865
1866
if had_site and not repo.has_site:
1867
# Remove the site
1868
result = celery_tasks.delete_site.delay(repo.route)
1869
1870
if repo.has_site == 2 or (had_site == 2 and had_site != repo.has_site):
1871
# Deploy all other sites which were destroyed by the primary site
1872
for other_repo in Repo.query.filter_by(owner=repo.owner, has_site=1):
1873
result = celery_tasks.copy_site.delay(other_repo.route)
1874
1875
return flask.redirect(f"/{username}/{repository}/settings", 303)
1876
1877
1878
@repositories.route("/<username>/<repository>/settings/add-label", methods=["POST"])
1879
def repository_settings_add_label(username, repository):
1880
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1881
flask.abort(401)
1882
1883
repo_data = db.session.get(Repo, f"/{username}/{repository}")
1884
1885
label = Label(repo_data, flask.request.form.get("label"), flask.request.form.get("colour"))
1886
db.session.add(label)
1887
db.session.commit()
1888
1889
return flask.redirect(f"/{username}/{repository}/settings", 303)
1890
1891
1892
@repositories.route("/<username>/<repository>/settings/delete-label", methods=["POST"])
1893
def repository_settings_delete_label(username, repository):
1894
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1895
flask.abort(401)
1896
1897
repo_data = db.session.get(Repo, f"/{username}/{repository}")
1898
1899
label = db.session.get(Label, flask.request.form.get("id"))
1900
1901
db.session.delete(label)
1902
db.session.commit()
1903
1904
return flask.redirect(f"/{username}/{repository}/settings", 303)
1905
1906
1907
@repositories.route("/<username>/<repository>/settings/edit-label", methods=["POST"])
1908
def repository_settings_edit_label(username, repository):
1909
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1910
flask.abort(401)
1911
1912
repo_data = db.session.get(Repo, f"/{username}/{repository}")
1913
1914
label = db.session.get(Label, flask.request.form.get("id"))
1915
1916
label.name = flask.request.form.get("label")
1917
label.colour_hex = flask.request.form.get("colour")
1918
1919
db.session.commit()
1920
1921
return flask.redirect(f"/{username}/{repository}/settings", 303)
1922
1923
1924
@repositories.route("/<username>/<repository>/settings/delete", methods=["POST"])
1925
def repository_settings_delete(username, repository):
1926
if username != flask.session.get("username"):
1927
flask.abort(401)
1928
1929
repo = db.session.get(Repo, f"/{username}/{repository}")
1930
1931
if not repo:
1932
flask.abort(404)
1933
1934
user = db.session.get(User, flask.session.get("username"))
1935
1936
if not bcrypt.check_password_hash(user.password_hashed, flask.request.form.get("password")):
1937
flask.flash(_("Incorrect password"), category="error")
1938
flask.abort(401)
1939
1940
if repo.has_site:
1941
celery_tasks.delete_site.delay(repo.route)
1942
1943
db.session.delete(repo)
1944
db.session.commit()
1945
1946
shutil.rmtree(os.path.join(config.REPOS_PATH, username, repository))
1947
1948
return flask.redirect(f"/{username}", 303)
1949
1950
1951
@app.errorhandler(404)
1952
def e404(error):
1953
return flask.render_template("errors/not-found.html"), 404
1954
1955
1956
@app.errorhandler(401)
1957
def e401(error):
1958
return flask.render_template("errors/unauthorised.html"), 401
1959
1960
1961
@app.errorhandler(403)
1962
def e403(error):
1963
return flask.render_template("errors/forbidden.html"), 403
1964
1965
1966
@app.errorhandler(418)
1967
def e418(error):
1968
return flask.render_template("errors/teapot.html"), 418
1969
1970
1971
@app.errorhandler(405)
1972
def e405(error):
1973
return flask.render_template("errors/method-not-allowed.html"), 405
1974
1975
1976
@app.errorhandler(500)
1977
def e500(error):
1978
return flask.render_template("errors/server-error.html"), 500
1979
1980
1981
@app.errorhandler(400)
1982
def e400(error):
1983
return flask.render_template("errors/bad-request.html"), 400
1984
1985
1986
@app.errorhandler(410)
1987
def e410(error):
1988
return flask.render_template("errors/gone.html"), 410
1989
1990
1991
@app.errorhandler(415)
1992
def e415(error):
1993
return flask.render_template("errors/media-type.html"), 415
1994
1995
1996
if __name__ == "__main__":
1997
app.run(debug=True, port=8080, host="0.0.0.0")
1998
1999
app.register_blueprint(repositories)
2000