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.66 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
all_posts = Post.query.filter(Post.repo == repo_data)
977
978
results = (all_posts
979
.filter(Post.subject.ilike(f"%{query}%") | Post.message.ilike(f"%{query}%"))
980
.order_by(Post.last_updated.desc()))
981
982
if flask.request.args.get("state"):
983
try:
984
results = results.filter(Post.state == int(flask.request.args.get("state")))
985
except ValueError:
986
pass # if state is not an integer, ignore it
987
988
if flask.request.args.get("label"):
989
results = results.filter(Post.labels.any(Label.identifier == flask.request.args.get("label")))
990
991
results = results.paginate(page=page_number, per_page=page_length)
992
993
if results.has_next:
994
next_page = results.next_num
995
else:
996
next_page = None
997
998
if results.has_prev:
999
prev_page = results.prev_num
1000
else:
1001
prev_page = None
1002
1003
return flask.render_template(
1004
"repo-forum-search.html",
1005
username=username,
1006
repository=repository,
1007
repo_data=repo_data,
1008
relationships=relationships,
1009
user_relationship=user_relationship,
1010
query=query,
1011
results=results,
1012
Post=Post,
1013
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1014
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1015
default_branch=repo_data.default_branch,
1016
page_number=page_number,
1017
page_length=page_length,
1018
next_page=next_page,
1019
prev_page=prev_page,
1020
num_pages=results.pages,
1021
require_state=flask.request.args.get("state"),
1022
require_label=flask.request.args.get("label"),
1023
)
1024
1025
1026
@repositories.route("/<username>/<repository>/forum/topic/<int:id>")
1027
def repository_forum_topic(username, repository, id):
1028
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1029
if not os.path.exists(server_repo_location):
1030
flask.abort(404)
1031
if not (get_visibility(username, repository) or get_permission_level(
1032
flask.session.get("username"), username,
1033
repository) is not None):
1034
flask.abort(403)
1035
1036
if not os.path.exists(server_repo_location):
1037
return flask.render_template("errors/not-found.html"), 404
1038
1039
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1040
user = User.query.filter_by(username=flask.session.get("username")).first()
1041
relationships = RepoAccess.query.filter_by(repo=repo_data)
1042
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1043
1044
post = Post.query.filter_by(id=id).first()
1045
1046
return flask.render_template(
1047
"repo-topic.html",
1048
username=username,
1049
repository=repository,
1050
repo_data=repo_data,
1051
relationships=relationships,
1052
user_relationship=user_relationship,
1053
post=post,
1054
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1055
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1056
default_branch=repo_data.default_branch
1057
)
1058
1059
1060
@repositories.route("/<username>/<repository>/forum/new", methods=["POST", "GET"])
1061
def repository_forum_new(username, repository):
1062
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1063
if not os.path.exists(server_repo_location):
1064
flask.abort(404)
1065
if not ((flask.session.get("username") and get_visibility(username, repository)) or get_permission_level(
1066
flask.session.get("username"), username,
1067
repository) is not None):
1068
flask.abort(403)
1069
1070
if not os.path.exists(server_repo_location):
1071
return flask.render_template("errors/not-found.html"), 404
1072
1073
repo = git.Repo(server_repo_location)
1074
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1075
user = User.query.filter_by(username=flask.session.get("username")).first()
1076
relationships = RepoAccess.query.filter_by(repo=repo_data)
1077
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1078
1079
post = Post(user, repo_data, None, flask.request.form["subject"],
1080
flask.request.form["message"])
1081
1082
db.session.add(post)
1083
db.session.commit()
1084
1085
return flask.redirect(
1086
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1087
post_id=post.number),
1088
code=303)
1089
1090
1091
@repositories.route("/<username>/<repository>/forum/<int:post_id>")
1092
def repository_forum_thread(username, repository, post_id):
1093
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1094
if not os.path.exists(server_repo_location):
1095
flask.abort(404)
1096
if not (get_visibility(username, repository) or get_permission_level(
1097
flask.session.get("username"), username,
1098
repository) is not None):
1099
flask.abort(403)
1100
1101
if not os.path.exists(server_repo_location):
1102
return flask.render_template("errors/not-found.html"), 404
1103
1104
repo = git.Repo(server_repo_location)
1105
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1106
user = User.query.filter_by(username=flask.session.get("username")).first()
1107
relationships = RepoAccess.query.filter_by(repo=repo_data)
1108
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1109
1110
if user:
1111
max_post_nesting = user.max_post_nesting
1112
else:
1113
max_post_nesting = 2
1114
1115
return flask.render_template(
1116
"repo-forum-thread.html",
1117
username=username,
1118
repository=repository,
1119
repo_data=repo_data,
1120
relationships=relationships,
1121
repo=repo,
1122
Post=Post,
1123
user_relationship=user_relationship,
1124
post_id=post_id,
1125
max_post_nesting=max_post_nesting,
1126
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1127
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1128
parent=Post.query.filter_by(repo=repo_data, number=post_id).first(),
1129
has_permission=not ((not get_permission_level(flask.session.get("username"), username,
1130
repository)) and db.session.get(Post,
1131
f"/{username}/{repository}/{post_id}").owner.username != flask.session.get("username")),
1132
)
1133
1134
1135
@repositories.route("/<username>/<repository>/forum/<int:post_id>/change-state",
1136
methods=["POST"])
1137
def repository_forum_change_state(username, repository, post_id):
1138
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1139
if not os.path.exists(server_repo_location):
1140
flask.abort(404)
1141
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"):
1142
flask.abort(403)
1143
1144
repo = git.Repo(server_repo_location)
1145
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1146
user = User.query.filter_by(username=flask.session.get("username")).first()
1147
relationships = RepoAccess.query.filter_by(repo=repo_data)
1148
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1149
1150
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1151
1152
if not post:
1153
flask.abort(404)
1154
if post.parent:
1155
flask.abort(400)
1156
1157
post.state = int(flask.request.form["new-state"])
1158
1159
db.session.commit()
1160
1161
return flask.redirect(
1162
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1163
post_id=post_id),
1164
code=303)
1165
1166
1167
@repositories.route("/<username>/<repository>/forum/<int:post_id>/reply", methods=["POST"])
1168
def repository_forum_reply(username, repository, post_id):
1169
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1170
if not os.path.exists(server_repo_location):
1171
flask.abort(404)
1172
if not ((flask.session.get("username") and get_visibility(username, repository)) or get_permission_level(
1173
flask.session.get("username"), username,
1174
repository) is not None):
1175
flask.abort(403)
1176
1177
if not os.path.exists(server_repo_location):
1178
return flask.render_template("errors/not-found.html"), 404
1179
1180
repo = git.Repo(server_repo_location)
1181
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1182
user = User.query.filter_by(username=flask.session.get("username")).first()
1183
relationships = RepoAccess.query.filter_by(repo=repo_data)
1184
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1185
if not user:
1186
flask.abort(401)
1187
1188
parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1189
post = Post(user, repo_data, parent, flask.request.form["subject"],
1190
flask.request.form["message"])
1191
1192
db.session.add(post)
1193
post.update_date()
1194
db.session.commit()
1195
1196
return flask.redirect(
1197
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1198
post_id=post_id),
1199
code=303)
1200
1201
1202
@repositories.route("/<username>/<repository>/forum/<int:post_id>/edit", methods=["POST"])
1203
def repository_forum_edit(username, repository, post_id):
1204
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1205
if not os.path.exists(server_repo_location):
1206
flask.abort(404)
1207
if not (get_visibility(username, repository) or get_permission_level(
1208
flask.session.get("username"), username,
1209
repository) is not None):
1210
flask.abort(403)
1211
1212
if not os.path.exists(server_repo_location):
1213
return flask.render_template("errors/not-found.html"), 404
1214
1215
repo = git.Repo(server_repo_location)
1216
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1217
user = User.query.filter_by(username=flask.session.get("username")).first()
1218
relationships = RepoAccess.query.filter_by(repo=repo_data)
1219
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1220
if not user:
1221
flask.abort(401)
1222
post = db.session.get(Post, f"/{username}/{repository}/{post_id}")
1223
if user != post.owner:
1224
flask.abort(403)
1225
1226
post.subject = flask.request.form["subject"]
1227
post.message = flask.request.form["message"]
1228
post.html = markdown.markdown2html(post.message).prettify()
1229
post.update_date()
1230
db.session.commit()
1231
1232
return flask.redirect(
1233
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1234
post_id=post_id),
1235
code=303)
1236
1237
1238
@repositories.route("/<username>/<repository>/forum/<int:post_id>/edit", methods=["GET"])
1239
def repository_forum_edit_form(username, repository, post_id):
1240
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1241
if not os.path.exists(server_repo_location):
1242
flask.abort(404)
1243
if not (get_visibility(username, repository) or get_permission_level(
1244
flask.session.get("username"), username,
1245
repository) is not None):
1246
flask.abort(403)
1247
1248
if not os.path.exists(server_repo_location):
1249
return flask.render_template("errors/not-found.html"), 404
1250
1251
repo = git.Repo(server_repo_location)
1252
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1253
user = User.query.filter_by(username=flask.session.get("username")).first()
1254
relationships = RepoAccess.query.filter_by(repo=repo_data)
1255
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1256
if not user:
1257
flask.abort(401)
1258
post = db.session.get(Post, f"/{username}/{repository}/{post_id}")
1259
if user != post.owner:
1260
flask.abort(403)
1261
1262
return flask.render_template(
1263
"repo-forum-edit.html",
1264
username=username,
1265
repository=repository,
1266
repo_data=repo_data,
1267
relationships=relationships,
1268
repo=repo,
1269
user_relationship=user_relationship,
1270
post=post,
1271
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1272
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1273
default_branch=repo_data.default_branch
1274
)
1275
1276
@repositories.route("/<username>/<repository>/forum/<int:post_id>/voteup",
1277
defaults={"score": 1})
1278
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votedown",
1279
defaults={"score": -1})
1280
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votes", defaults={"score": 0})
1281
def repository_forum_vote(username, repository, post_id, score):
1282
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1283
if not os.path.exists(server_repo_location):
1284
flask.abort(404)
1285
if not (get_visibility(username, repository) or get_permission_level(
1286
flask.session.get("username"), username,
1287
repository) is not None):
1288
flask.abort(403)
1289
1290
if not os.path.exists(server_repo_location):
1291
return flask.render_template("errors/not-found.html"), 404
1292
1293
repo = git.Repo(server_repo_location)
1294
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1295
user = User.query.filter_by(username=flask.session.get("username")).first()
1296
relationships = RepoAccess.query.filter_by(repo=repo_data)
1297
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1298
if not user:
1299
flask.abort(401)
1300
1301
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1302
1303
if score:
1304
old_relationship = PostVote.query.filter_by(user_username=user.username,
1305
post_identifier=post.identifier).first()
1306
if old_relationship:
1307
if score == old_relationship.vote_score:
1308
db.session.delete(old_relationship)
1309
post.vote_sum -= old_relationship.vote_score
1310
else:
1311
post.vote_sum -= old_relationship.vote_score
1312
post.vote_sum += score
1313
old_relationship.vote_score = score
1314
else:
1315
relationship = PostVote(user, post, score)
1316
post.vote_sum += score
1317
db.session.add(relationship)
1318
1319
db.session.commit()
1320
1321
user_vote = PostVote.query.filter_by(user_username=user.username,
1322
post_identifier=post.identifier).first()
1323
response = flask.make_response(
1324
str(post.vote_sum) + " " + str(user_vote.vote_score if user_vote else 0))
1325
response.content_type = "text/plain"
1326
1327
return response
1328
1329
1330
@repositories.route("/<username>/<repository>/forum/<int:post_id>/label", methods=["POST"])
1331
def repository_forum_label(username, repository, post_id):
1332
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1333
if not os.path.exists(server_repo_location):
1334
flask.abort(404)
1335
if not get_permission_level(flask.session.get("username"), username, repository):
1336
flask.abort(403)
1337
1338
repo = git.Repo(server_repo_location)
1339
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1340
user = User.query.filter_by(username=flask.session.get("username")).first()
1341
relationships = RepoAccess.query.filter_by(repo=repo_data)
1342
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1343
1344
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1345
1346
if not post:
1347
flask.abort(404)
1348
if post.parent:
1349
flask.abort(400)
1350
1351
label = db.session.get(Label, flask.request.form["label"])
1352
1353
if PostLabel.query.filter_by(post=post, label=label).first():
1354
return flask.redirect(
1355
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1356
post_id=post_id),
1357
code=303)
1358
1359
post_label = PostLabel(post, label)
1360
db.session.add(post_label)
1361
1362
db.session.commit()
1363
1364
return flask.redirect(
1365
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1366
post_id=post_id),
1367
code=303)
1368
1369
1370
@repositories.route("/<username>/<repository>/forum/<int:post_id>/remove-label")
1371
def repository_forum_remove_label(username, repository, post_id):
1372
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1373
if not os.path.exists(server_repo_location):
1374
flask.abort(404)
1375
if not get_permission_level(flask.session.get("username"), username, repository):
1376
flask.abort(403)
1377
1378
repo = git.Repo(server_repo_location)
1379
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1380
user = User.query.filter_by(username=flask.session.get("username")).first()
1381
relationships = RepoAccess.query.filter_by(repo=repo_data)
1382
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1383
1384
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1385
1386
if not post:
1387
flask.abort(404)
1388
if post.parent:
1389
flask.abort(400)
1390
1391
label = db.session.get(Label, flask.request.args["label"])
1392
1393
post_label = PostLabel.query.filter_by(post=post, label=label).first()
1394
db.session.delete(post_label)
1395
1396
db.session.commit()
1397
1398
return flask.redirect(
1399
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1400
post_id=post_id),
1401
code=303)
1402
1403
1404
@repositories.route("/<username>/<repository>/favourite")
1405
def repository_favourite(username, repository):
1406
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1407
if not os.path.exists(server_repo_location):
1408
flask.abort(404)
1409
if not (get_visibility(username, repository) or get_permission_level(
1410
flask.session.get("username"), username,
1411
repository) is not None):
1412
flask.abort(403)
1413
1414
if not os.path.exists(server_repo_location):
1415
return flask.render_template("errors/not-found.html"), 404
1416
1417
repo = git.Repo(server_repo_location)
1418
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1419
user = User.query.filter_by(username=flask.session.get("username")).first()
1420
relationships = RepoAccess.query.filter_by(repo=repo_data)
1421
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1422
if not user:
1423
flask.abort(401)
1424
1425
old_relationship = RepoFavourite.query.filter_by(user_username=user.username,
1426
repo_route=repo_data.route).first()
1427
if old_relationship:
1428
db.session.delete(old_relationship)
1429
else:
1430
relationship = RepoFavourite(user, repo_data)
1431
db.session.add(relationship)
1432
1433
db.session.commit()
1434
1435
return flask.redirect(flask.url_for("favourites"), code=303)
1436
1437
1438
@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"])
1439
def repository_users(username, repository):
1440
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1441
if not os.path.exists(server_repo_location):
1442
flask.abort(404)
1443
if not (get_visibility(username, repository) or get_permission_level(
1444
flask.session.get("username"), username,
1445
repository) is not None):
1446
flask.abort(403)
1447
1448
if not os.path.exists(server_repo_location):
1449
return flask.render_template("errors/not-found.html"), 404
1450
1451
repo = git.Repo(server_repo_location)
1452
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1453
user = User.query.filter_by(username=flask.session.get("username")).first()
1454
relationships = RepoAccess.query.filter_by(repo=repo_data)
1455
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1456
1457
if flask.request.method == "GET":
1458
return flask.render_template(
1459
"repo-users.html",
1460
username=username,
1461
repository=repository,
1462
repo_data=repo_data,
1463
relationships=relationships,
1464
repo=repo,
1465
user_relationship=user_relationship,
1466
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1467
is_favourite=get_favourite(flask.session.get("username"), username, repository)
1468
)
1469
else:
1470
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1471
flask.abort(401)
1472
1473
if flask.request.form.get("new-username"):
1474
# Create new relationship
1475
new_user = User.query.filter_by(
1476
username=flask.request.form.get("new-username")).first()
1477
relationship = RepoAccess(new_user, repo_data, flask.request.form.get("new-level"))
1478
db.session.add(relationship)
1479
db.session.commit()
1480
if flask.request.form.get("update-username"):
1481
# Create new relationship
1482
updated_user = User.query.filter_by(
1483
username=flask.request.form.get("update-username")).first()
1484
relationship = RepoAccess.query.filter_by(repo=repo_data, user=updated_user).first()
1485
if flask.request.form.get("update-level") == -1:
1486
relationship.delete()
1487
else:
1488
relationship.access_level = flask.request.form.get("update-level")
1489
db.session.commit()
1490
1491
return flask.redirect(
1492
app.url_for(".repository_users", username=username, repository=repository))
1493
1494
1495
@repositories.route("/<username>/<repository>/branches/")
1496
def repository_branches(username, repository):
1497
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1498
if not os.path.exists(server_repo_location):
1499
flask.abort(404)
1500
if not (get_visibility(username, repository) or get_permission_level(
1501
flask.session.get("username"), username,
1502
repository) is not None):
1503
flask.abort(403)
1504
1505
if not os.path.exists(server_repo_location):
1506
return flask.render_template("errors/not-found.html"), 404
1507
1508
repo = git.Repo(server_repo_location)
1509
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1510
1511
return flask.render_template(
1512
"repo-branches.html",
1513
username=username,
1514
repository=repository,
1515
repo_data=repo_data,
1516
repo=repo,
1517
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1518
is_favourite=get_favourite(flask.session.get("username"), username, repository)
1519
)
1520
1521
1522
@repositories.route("/<username>/<repository>/log/", defaults={"branch": None})
1523
@repositories.route("/<username>/<repository>/log/<branch>/")
1524
def repository_log(username, repository, branch):
1525
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1526
if not os.path.exists(server_repo_location):
1527
flask.abort(404)
1528
if not (get_visibility(username, repository) or get_permission_level(
1529
flask.session.get("username"), username,
1530
repository) is not None):
1531
flask.abort(403)
1532
1533
if not os.path.exists(server_repo_location):
1534
return flask.render_template("errors/not-found.html"), 404
1535
1536
repo = git.Repo(server_repo_location)
1537
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1538
if not repo_data.default_branch:
1539
if repo.heads:
1540
repo_data.default_branch = repo.heads[0].name
1541
else:
1542
return flask.render_template("empty.html",
1543
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
1544
if not branch:
1545
branch = repo_data.default_branch
1546
return flask.redirect(f"./{branch}", code=302)
1547
1548
if branch.startswith("tag:"):
1549
ref = f"tags/{branch[4:]}"
1550
elif branch.startswith("~"):
1551
ref = branch[1:]
1552
else:
1553
ref = f"heads/{branch}"
1554
1555
ref = ref.replace("~", "/") # encode slashes for URL support
1556
1557
try:
1558
repo.git.checkout("-f", ref)
1559
except git.exc.GitCommandError:
1560
return flask.render_template("errors/not-found.html"), 404
1561
1562
branches = repo.heads
1563
1564
all_refs = []
1565
for ref in repo.heads:
1566
all_refs.append((ref, "head"))
1567
for ref in repo.tags:
1568
all_refs.append((ref, "tag"))
1569
1570
commit_list = [f"/{username}/{repository}/{sha}" for sha in
1571
git_command(server_repo_location, None, "log",
1572
"--format='%H'").decode().split("\n")]
1573
1574
commits = Commit.query.filter(Commit.identifier.in_(commit_list)).order_by(Commit.author_date.desc())
1575
page_number = flask.request.args.get("page", 1, type=int)
1576
if flask.session.get("username"):
1577
default_page_length = db.session.get(User, flask.session.get("username")).default_page_length
1578
else:
1579
default_page_length = 16
1580
page_length = flask.request.args.get("per_page", default_page_length, type=int)
1581
page_listing = db.paginate(commits, page=page_number, per_page=page_length)
1582
1583
if page_listing.has_next:
1584
next_page = page_listing.next_num
1585
else:
1586
next_page = None
1587
1588
if page_listing.has_prev:
1589
prev_page = page_listing.prev_num
1590
else:
1591
prev_page = None
1592
1593
return flask.render_template(
1594
"repo-log.html",
1595
username=username,
1596
repository=repository,
1597
branches=all_refs,
1598
current=branch,
1599
repo_data=repo_data,
1600
repo=repo,
1601
commits=page_listing,
1602
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1603
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1604
page_number=page_number,
1605
page_length=page_length,
1606
next_page=next_page,
1607
prev_page=prev_page,
1608
num_pages=page_listing.pages
1609
)
1610
1611
1612
@repositories.route("/<username>/<repository>/prs/", methods=["GET", "POST"])
1613
def repository_prs(username, repository):
1614
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1615
if not os.path.exists(server_repo_location):
1616
flask.abort(404)
1617
if not (get_visibility(username, repository) or get_permission_level(
1618
flask.session.get("username"), username,
1619
repository) is not None):
1620
flask.abort(403)
1621
1622
if not os.path.exists(server_repo_location):
1623
return flask.render_template("errors/not-found.html"), 404
1624
1625
if flask.request.method == "GET":
1626
repo = git.Repo(server_repo_location)
1627
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1628
user = User.query.filter_by(username=flask.session.get("username")).first()
1629
1630
return flask.render_template(
1631
"repo-prs.html",
1632
username=username,
1633
repository=repository,
1634
repo_data=repo_data,
1635
repo=repo,
1636
PullRequest=PullRequest,
1637
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1638
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1639
default_branch=repo_data.default_branch,
1640
branches=repo.branches
1641
)
1642
1643
elif "id" not in flask.request.form:
1644
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1645
head = flask.request.form.get("head")
1646
head_route = flask.request.form.get("headroute")
1647
base = flask.request.form.get("base")
1648
1649
if not head and base and head_route:
1650
return flask.redirect(".", 400)
1651
1652
head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/")))
1653
base_repo = git.Repo(server_repo_location)
1654
1655
if head not in head_repo.branches or base not in base_repo.branches:
1656
flask.flash(Markup(
1657
"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Bad branch name")),
1658
category="error")
1659
return flask.redirect(".", 303)
1660
1661
head_data = db.session.get(Repo, head_route)
1662
if not head_data.visibility:
1663
flask.flash(Markup(
1664
"<iconify-icon icon='mdi:error'></iconify-icon>" + _(
1665
"Head can't be restricted")),
1666
category="error")
1667
return flask.redirect(".", 303)
1668
1669
pull_request = PullRequest(head_data, head, repo_data, base,
1670
db.session.get(User, flask.session["username"]))
1671
1672
db.session.add(pull_request)
1673
db.session.commit()
1674
1675
# Create the notification
1676
notification = Notification({"type": "pr", "head": pull_request.head.route, "base": pull_request.base.route, "pr": pull_request.id})
1677
db.session.add(notification)
1678
db.session.commit()
1679
1680
# Send a notification to all users who have enabled PR notifications for this repo
1681
for relationship in RepoFavourite.query.filter_by(repo_route=pull_request.base.route, notify_pr=True).all():
1682
user = relationship.user
1683
user_notification = UserNotification(user, notification, 1)
1684
db.session.add(user_notification)
1685
db.session.commit()
1686
celery_tasks.send_notification.apply_async(args=[user_notification.id])
1687
1688
return flask.redirect(".", 303)
1689
else:
1690
id = flask.request.form.get("id")
1691
pull_request = db.session.get(PullRequest, id)
1692
1693
if not pull_request:
1694
flask.abort(404)
1695
1696
if not (get_visibility(username, repository) or get_permission_level(
1697
flask.session.get("username"), username,
1698
repository) >= 1 or pull_request.owner.username == flask.session.get("username")):
1699
flask.abort(403)
1700
1701
if not get_permission_level(flask.session.get("username"), username, repository):
1702
flask.abort(401)
1703
1704
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1705
1706
if pull_request:
1707
pull_request.resolves_list = flask.request.form.get("resolves")
1708
db.session.commit()
1709
1710
return flask.redirect(".", 303)
1711
1712
1713
@repositories.route("/<username>/<repository>/prs/merge", methods=["POST"])
1714
def repository_prs_merge(username, repository):
1715
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1716
if not os.path.exists(server_repo_location):
1717
flask.abort(404)
1718
if not (get_visibility(username, repository) or get_permission_level(
1719
flask.session.get("username"), username,
1720
repository) is not None):
1721
flask.abort(403)
1722
1723
if not get_permission_level(flask.session.get("username"), username, repository):
1724
flask.abort(401)
1725
1726
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1727
repo = git.Repo(server_repo_location)
1728
id = flask.request.form.get("id")
1729
1730
pull_request = db.session.get(PullRequest, id)
1731
1732
if pull_request:
1733
result = celery_tasks.merge_heads.delay(
1734
pull_request.head_route,
1735
pull_request.head_branch,
1736
pull_request.base_route,
1737
pull_request.base_branch,
1738
pull_request.id,
1739
simulate=True
1740
)
1741
task_result = worker.AsyncResult(result.id)
1742
1743
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303) # should be 202 Accepted but we must use a redirect
1744
# db.session.delete(pull_request)
1745
# db.session.commit()
1746
else:
1747
flask.abort(400)
1748
1749
1750
@repositories.route("/<username>/<repository>/prs/<int:id>/merge")
1751
def repository_prs_merge_stage_two(username, repository, id):
1752
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1753
if not os.path.exists(server_repo_location):
1754
flask.abort(404)
1755
if not (get_visibility(username, repository) or get_permission_level(
1756
flask.session.get("username"), username,
1757
repository) is not None):
1758
flask.abort(403)
1759
1760
if not get_permission_level(flask.session.get("username"), username, repository):
1761
flask.abort(401)
1762
1763
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1764
repo = git.Repo(server_repo_location)
1765
1766
pull_request = db.session.get(PullRequest, id)
1767
1768
if pull_request:
1769
result = celery_tasks.merge_heads.delay(
1770
pull_request.head_route,
1771
pull_request.head_branch,
1772
pull_request.base_route,
1773
pull_request.base_branch,
1774
pull_request.id,
1775
simulate=False
1776
)
1777
task_result = worker.AsyncResult(result.id)
1778
1779
db.session.commit()
1780
1781
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
1782
# db.session.delete(pull_request)
1783
else:
1784
flask.abort(400)
1785
1786
1787
@app.route("/task/<task_id>")
1788
def task_monitor(task_id):
1789
task_result = worker.AsyncResult(task_id)
1790
1791
if flask.request.args.get("partial"):
1792
# htmx partial update
1793
return render_block("task-monitor.html", "content", result=task_result, query_string=flask.request.query_string.decode(), delay=1000)
1794
1795
# Since most tasks finish rather quickly, the initial delay is faster, so it doesn't wait for too long
1796
return flask.render_template("task-monitor.html", result=task_result, query_string=flask.request.query_string.decode(), delay=125)
1797
1798
1799
@repositories.route("/<username>/<repository>/prs/delete", methods=["POST"])
1800
def repository_prs_delete(username, repository):
1801
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1802
if not os.path.exists(server_repo_location):
1803
flask.abort(404)
1804
if not (get_visibility(username, repository) or get_permission_level(
1805
flask.session.get("username"), username,
1806
repository) is not None):
1807
flask.abort(403)
1808
1809
if not get_permission_level(flask.session.get("username"), username, repository):
1810
flask.abort(401)
1811
1812
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1813
repo = git.Repo(server_repo_location)
1814
id = flask.request.form.get("id")
1815
1816
pull_request = db.session.get(PullRequest, id)
1817
1818
if pull_request:
1819
pull_request.state = 2
1820
db.session.commit()
1821
1822
return flask.redirect(".", 303)
1823
1824
1825
@repositories.route("/<username>/<repository>/settings/")
1826
def repository_settings(username, repository):
1827
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1828
flask.abort(401)
1829
1830
repo = git.Repo(os.path.join(config.REPOS_PATH, username, repository))
1831
1832
site_link = Markup(f"<code>http{'s' if config.suggest_https else ''}://{username}.{config.BASE_DOMAIN}/{repository}</code>")
1833
primary_site_link = Markup(f"<code>http{'s' if config.suggest_https else ''}://{username}.{config.BASE_DOMAIN}/</code>")
1834
1835
return flask.render_template("repo-settings.html", username=username, repository=repository,
1836
repo_data=db.session.get(Repo, f"/{username}/{repository}"),
1837
branches=[branch.name for branch in repo.branches],
1838
site_link=site_link, primary_site_link=primary_site_link,
1839
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1840
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1841
)
1842
1843
1844
@repositories.route("/<username>/<repository>/settings/", methods=["POST"])
1845
def repository_settings_post(username, repository):
1846
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1847
flask.abort(401)
1848
1849
repo = db.session.get(Repo, f"/{username}/{repository}")
1850
1851
repo.visibility = flask.request.form.get("visibility", type=int)
1852
repo.info = flask.request.form.get("description")
1853
repo.default_branch = flask.request.form.get("default_branch")
1854
repo.url = flask.request.form.get("url")
1855
1856
# Update site settings
1857
had_site = repo.has_site
1858
old_branch = repo.site_branch
1859
if flask.request.form.get("site_branch"):
1860
repo.site_branch = flask.request.form.get("site_branch")
1861
if flask.request.form.get("primary_site"):
1862
if had_site != 2:
1863
# Remove primary site from other repos
1864
for other_repo in Repo.query.filter_by(owner=repo.owner, has_site=2):
1865
other_repo.has_site = 1 # switch it to a regular site
1866
flask.flash(Markup(
1867
_("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(
1868
repository=other_repo.route
1869
)), category="warning")
1870
repo.has_site = 2
1871
else:
1872
repo.has_site = 1
1873
else:
1874
repo.site_branch = None
1875
repo.has_site = 0
1876
1877
db.session.commit()
1878
1879
if not (had_site, old_branch) == (repo.has_site, repo.site_branch):
1880
# Deploy the newly activated site
1881
result = celery_tasks.copy_site.delay(repo.route)
1882
1883
if had_site and not repo.has_site:
1884
# Remove the site
1885
result = celery_tasks.delete_site.delay(repo.route)
1886
1887
if repo.has_site == 2 or (had_site == 2 and had_site != repo.has_site):
1888
# Deploy all other sites which were destroyed by the primary site
1889
for other_repo in Repo.query.filter_by(owner=repo.owner, has_site=1):
1890
result = celery_tasks.copy_site.delay(other_repo.route)
1891
1892
return flask.redirect(f"/{username}/{repository}/settings", 303)
1893
1894
1895
@repositories.route("/<username>/<repository>/settings/add-label", methods=["POST"])
1896
def repository_settings_add_label(username, repository):
1897
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1898
flask.abort(401)
1899
1900
repo_data = db.session.get(Repo, f"/{username}/{repository}")
1901
1902
label = Label(repo_data, flask.request.form.get("label"), flask.request.form.get("colour"))
1903
db.session.add(label)
1904
db.session.commit()
1905
1906
return flask.redirect(f"/{username}/{repository}/settings", 303)
1907
1908
1909
@repositories.route("/<username>/<repository>/settings/delete-label", methods=["POST"])
1910
def repository_settings_delete_label(username, repository):
1911
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1912
flask.abort(401)
1913
1914
repo_data = db.session.get(Repo, f"/{username}/{repository}")
1915
1916
label = db.session.get(Label, flask.request.form.get("id"))
1917
1918
db.session.delete(label)
1919
db.session.commit()
1920
1921
return flask.redirect(f"/{username}/{repository}/settings", 303)
1922
1923
1924
@repositories.route("/<username>/<repository>/settings/edit-label", methods=["POST"])
1925
def repository_settings_edit_label(username, repository):
1926
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1927
flask.abort(401)
1928
1929
repo_data = db.session.get(Repo, f"/{username}/{repository}")
1930
1931
label = db.session.get(Label, flask.request.form.get("id"))
1932
1933
label.name = flask.request.form.get("label")
1934
label.colour_hex = flask.request.form.get("colour")
1935
1936
db.session.commit()
1937
1938
return flask.redirect(f"/{username}/{repository}/settings", 303)
1939
1940
1941
@repositories.route("/<username>/<repository>/settings/delete", methods=["POST"])
1942
def repository_settings_delete(username, repository):
1943
if username != flask.session.get("username"):
1944
flask.abort(401)
1945
1946
repo = db.session.get(Repo, f"/{username}/{repository}")
1947
1948
if not repo:
1949
flask.abort(404)
1950
1951
user = db.session.get(User, flask.session.get("username"))
1952
1953
if not bcrypt.check_password_hash(user.password_hashed, flask.request.form.get("password")):
1954
flask.flash(_("Incorrect password"), category="error")
1955
flask.abort(401)
1956
1957
if repo.has_site:
1958
celery_tasks.delete_site.delay(repo.route)
1959
1960
db.session.delete(repo)
1961
db.session.commit()
1962
1963
shutil.rmtree(os.path.join(config.REPOS_PATH, username, repository))
1964
1965
return flask.redirect(f"/{username}", 303)
1966
1967
1968
@app.errorhandler(404)
1969
def e404(error):
1970
return flask.render_template("errors/not-found.html"), 404
1971
1972
1973
@app.errorhandler(401)
1974
def e401(error):
1975
return flask.render_template("errors/unauthorised.html"), 401
1976
1977
1978
@app.errorhandler(403)
1979
def e403(error):
1980
return flask.render_template("errors/forbidden.html"), 403
1981
1982
1983
@app.errorhandler(418)
1984
def e418(error):
1985
return flask.render_template("errors/teapot.html"), 418
1986
1987
1988
@app.errorhandler(405)
1989
def e405(error):
1990
return flask.render_template("errors/method-not-allowed.html"), 405
1991
1992
1993
@app.errorhandler(500)
1994
def e500(error):
1995
return flask.render_template("errors/server-error.html"), 500
1996
1997
1998
@app.errorhandler(400)
1999
def e400(error):
2000
return flask.render_template("errors/bad-request.html"), 400
2001
2002
2003
@app.errorhandler(410)
2004
def e410(error):
2005
return flask.render_template("errors/gone.html"), 410
2006
2007
2008
@app.errorhandler(415)
2009
def e415(error):
2010
return flask.render_template("errors/media-type.html"), 415
2011
2012
2013
if __name__ == "__main__":
2014
app.run(debug=True, port=8080, host="0.0.0.0")
2015
2016
app.register_blueprint(repositories)
2017