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.77 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
response = flask.send_from_directory(config.REPOS_PATH,
665
os.path.join(username, repository, subpath))
666
667
if repo_data.visibility < 2:
668
response.headers["X-Robots-Tag"] = "noindex"
669
670
return response
671
672
673
@repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""})
674
@repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""})
675
@repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>")
676
def repository_tree(username, repository, branch, subpath):
677
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
678
if not os.path.exists(server_repo_location):
679
flask.abort(404)
680
if not (get_visibility(username, repository) or get_permission_level(
681
flask.session.get("username"), username,
682
repository) is not None):
683
flask.abort(403)
684
685
repo = git.Repo(server_repo_location)
686
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
687
if not repo_data.default_branch:
688
if repo.heads:
689
repo_data.default_branch = repo.heads[0].name
690
else:
691
return flask.render_template("empty.html",
692
remote=f"{config.www_protocol}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
693
if not branch:
694
branch = repo_data.default_branch
695
return flask.redirect(f"./{branch}", code=302)
696
697
if branch.startswith("tag:"):
698
ref = f"tags/{branch[4:]}"
699
elif branch.startswith("~"):
700
ref = branch[1:]
701
else:
702
ref = f"heads/{branch}"
703
704
ref = ref.replace("~", "/") # encode slashes for URL support
705
706
try:
707
repo.git.checkout("-f", ref)
708
except git.exc.GitCommandError:
709
return flask.render_template("errors/not-found.html"), 404
710
711
branches = repo.heads
712
713
all_refs = []
714
for ref in repo.heads:
715
all_refs.append((ref, "head"))
716
for ref in repo.tags:
717
all_refs.append((ref, "tag"))
718
719
if os.path.isdir(os.path.join(server_repo_location, subpath)):
720
files = []
721
blobs = []
722
723
for entry in os.listdir(os.path.join(server_repo_location, subpath)):
724
if not os.path.basename(entry) == ".git":
725
files.append(os.path.join(subpath, entry))
726
727
infos = []
728
729
for file in files:
730
path = os.path.join(server_repo_location, file)
731
mimetype = guess_mime(path)
732
733
text = git_command(server_repo_location, None, "log", "--format='%H\n'",
734
shlex.quote(file)).decode()
735
736
sha = text.split("\n")[0]
737
identifier = f"/{username}/{repository}/{sha}"
738
739
last_commit = db.session.get(Commit, identifier)
740
741
info = {
742
"name": os.path.basename(file),
743
"serverPath": path,
744
"relativePath": file,
745
"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file),
746
"size": human_size(os.path.getsize(path)),
747
"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}",
748
"commit": last_commit,
749
"shaSize": 7,
750
}
751
752
special_icon = config.match_icon(os.path.basename(file))
753
if special_icon:
754
info["icon"] = special_icon
755
elif os.path.isdir(path):
756
info["icon"] = config.folder_icon
757
info["size"] = _("{} files").format(len(os.listdir(path)))
758
elif mimetypes.guess_type(path)[0] in config.file_icons:
759
info["icon"] = config.file_icons[mimetypes.guess_type(path)[0]]
760
else:
761
info["icon"] = config.unknown_icon
762
763
if os.path.isdir(path):
764
infos.insert(0, info)
765
else:
766
infos.append(info)
767
768
return flask.render_template(
769
"repo-tree.html",
770
username=username,
771
repository=repository,
772
files=infos,
773
subpath=os.path.join("/", subpath),
774
branches=all_refs,
775
current=branch,
776
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
777
is_favourite=get_favourite(flask.session.get("username"), username, repository),
778
repo_data=repo_data,
779
)
780
else:
781
path = os.path.join(server_repo_location, subpath)
782
783
if not os.path.exists(path):
784
return flask.render_template("errors/not-found.html"), 404
785
786
mimetype = guess_mime(path)
787
mode = mimetype.split("/", 1)[0]
788
size = human_size(os.path.getsize(path))
789
790
special_icon = config.match_icon(os.path.basename(path))
791
if special_icon:
792
icon = special_icon
793
elif os.path.isdir(path):
794
icon = config.folder_icon
795
elif mimetypes.guess_type(path)[0] in config.file_icons:
796
icon = config.file_icons[mimetypes.guess_type(path)[0]]
797
else:
798
icon = config.unknown_icon
799
800
contents = None
801
if mode == "text":
802
contents = convert_to_html(path)
803
804
return flask.render_template(
805
"repo-file.html",
806
username=username,
807
repository=repository,
808
file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath),
809
branches=all_refs,
810
current=branch,
811
mode=mode,
812
mimetype=mimetype,
813
detailedtype=magic.from_file(path),
814
size=size,
815
icon=icon,
816
subpath=os.path.join("/", subpath),
817
extension=pathlib.Path(path).suffix,
818
basename=os.path.basename(path),
819
contents=contents,
820
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
821
is_favourite=get_favourite(flask.session.get("username"), username, repository),
822
repo_data=repo_data,
823
)
824
825
826
@repositories.route("/<username>/<repository>/commit/<sha>")
827
def repository_commit(username, repository, sha):
828
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
829
if not os.path.exists(server_repo_location):
830
flask.abort(404)
831
if not (get_visibility(username, repository) or get_permission_level(
832
flask.session.get("username"), username,
833
repository) is not None):
834
flask.abort(403)
835
836
if not os.path.exists(server_repo_location):
837
return flask.render_template("errors/not-found.html"), 404
838
839
repo = git.Repo(server_repo_location)
840
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
841
842
files = git_command(os.path.join(server_repo_location, ".git"), None, "diff-tree", "-r",
843
"--name-only", "--no-commit-id", sha).decode().split("\n")[:-1]
844
845
return flask.render_template(
846
"repo-commit.html",
847
username=username,
848
repository=repository,
849
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
850
is_favourite=get_favourite(flask.session.get("username"), username, repository),
851
diff={file: git_command(os.path.join(server_repo_location, ".git"), None, "diff",
852
str(sha) + "^!", "--", file).decode().split("\n") for
853
file in files},
854
data=db.session.get(Commit, f"/{username}/{repository}/{sha}"),
855
repo_data=repo_data,
856
comment_query=Comment.query,
857
permission_level=get_permission_level(flask.session.get("username"), username, repository),
858
)
859
860
861
@repositories.route("/<username>/<repository>/commit/<sha>/add_comment", methods=["POST"])
862
def repository_commit_add_comment(username, repository, sha):
863
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
864
if not os.path.exists(server_repo_location):
865
flask.abort(404)
866
if not (get_visibility(username, repository) or get_permission_level(
867
flask.session.get("username"), username,
868
repository) is not None):
869
flask.abort(403)
870
871
comment = Comment(
872
db.session.get(User, flask.session.get("username")),
873
db.session.get(Repo, f"/{username}/{repository}"),
874
db.session.get(Commit, f"/{username}/{repository}/{sha}"),
875
flask.request.form["comment"],
876
flask.request.form["file"],
877
flask.request.form["line"],
878
)
879
880
db.session.add(comment)
881
db.session.commit()
882
883
return flask.redirect(
884
flask.url_for(".repository_commit", username=username, repository=repository, sha=sha),
885
code=303
886
)
887
888
889
@repositories.route("/<username>/<repository>/commit/<sha>/delete_comment/<int:id>", methods=["POST"])
890
def repository_commit_delete_comment(username, repository, sha, id):
891
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
892
comment = Comment.query.filter_by(identifier=f"/{username}/{repository}/{id}").first()
893
commit = Commit.query.filter_by(identifier=f"/{username}/{repository}/{sha}").first()
894
if (
895
comment.owner.username == flask.session.get("username")
896
or get_permission_level(flask.session.get("username"), username, repository) >= 2
897
or comment.commit.owner.username == flask.session.get("username")
898
):
899
db.session.delete(comment)
900
db.session.commit()
901
902
return flask.redirect(
903
flask.url_for(".repository_commit", username=username, repository=repository, sha=sha),
904
code=303
905
)
906
907
908
@repositories.route("/<username>/<repository>/commit/<sha>/resolve_comment/<int:id>", methods=["POST"])
909
def repository_commit_resolve_comment(username, repository, sha, id):
910
comment = Comment.query.filter_by(identifier=f"/{username}/{repository}/{id}").first()
911
if (
912
comment.commit.owner.username == flask.session.get("username")
913
or get_permission_level(flask.session.get("username"), username, repository) >= 2
914
or comment.owner.username == flask.session.get("username")
915
):
916
comment.state = int(not comment.state)
917
db.session.commit()
918
919
return flask.redirect(
920
flask.url_for(".repository_commit", username=username, repository=repository, sha=sha),
921
code=303
922
)
923
924
925
@repositories.route("/<username>/<repository>/forum/")
926
def repository_forum(username, repository):
927
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
928
if not os.path.exists(server_repo_location):
929
flask.abort(404)
930
if not (get_visibility(username, repository) or get_permission_level(
931
flask.session.get("username"), username,
932
repository) is not None):
933
flask.abort(403)
934
935
repo = git.Repo(server_repo_location)
936
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
937
user = User.query.filter_by(username=flask.session.get("username")).first()
938
relationships = RepoAccess.query.filter_by(repo=repo_data)
939
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
940
941
return flask.render_template(
942
"repo-forum.html",
943
username=username,
944
repository=repository,
945
repo_data=repo_data,
946
relationships=relationships,
947
repo=repo,
948
user_relationship=user_relationship,
949
Post=Post,
950
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
951
is_favourite=get_favourite(flask.session.get("username"), username, repository),
952
default_branch=repo_data.default_branch
953
)
954
955
956
@repositories.route("/<username>/<repository>/forum/search")
957
def repository_forum_search(username, repository):
958
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
959
if not os.path.exists(server_repo_location):
960
flask.abort(404)
961
if not (get_visibility(username, repository) or get_permission_level(
962
flask.session.get("username"), username,
963
repository) is not None):
964
flask.abort(403)
965
966
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
967
user = User.query.filter_by(username=flask.session.get("username")).first()
968
relationships = RepoAccess.query.filter_by(repo=repo_data)
969
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
970
971
query = flask.request.args.get("q")
972
973
page_number = flask.request.args.get("page", 1, type=int)
974
if flask.session.get("username"):
975
default_page_length = db.session.get(User, flask.session.get("username")).default_page_length
976
else:
977
default_page_length = 16
978
979
page_length = flask.request.args.get("per_page", default_page_length, type=int)
980
981
all_posts = Post.query.filter(Post.repo == repo_data)
982
983
results = (all_posts
984
.filter(Post.subject.ilike(f"%{query}%") | Post.message.ilike(f"%{query}%"))
985
.order_by(Post.last_updated.desc()))
986
987
if flask.request.args.get("state"):
988
try:
989
results = results.filter(Post.state == int(flask.request.args.get("state")))
990
except ValueError:
991
pass # if state is not an integer, ignore it
992
993
if flask.request.args.get("label"):
994
results = results.filter(Post.labels.any(Label.identifier == flask.request.args.get("label")))
995
996
results = results.paginate(page=page_number, per_page=page_length)
997
998
if results.has_next:
999
next_page = results.next_num
1000
else:
1001
next_page = None
1002
1003
if results.has_prev:
1004
prev_page = results.prev_num
1005
else:
1006
prev_page = None
1007
1008
return flask.render_template(
1009
"repo-forum-search.html",
1010
username=username,
1011
repository=repository,
1012
repo_data=repo_data,
1013
relationships=relationships,
1014
user_relationship=user_relationship,
1015
query=query,
1016
results=results,
1017
Post=Post,
1018
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1019
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1020
default_branch=repo_data.default_branch,
1021
page_number=page_number,
1022
page_length=page_length,
1023
next_page=next_page,
1024
prev_page=prev_page,
1025
num_pages=results.pages,
1026
require_state=flask.request.args.get("state"),
1027
require_label=flask.request.args.get("label"),
1028
)
1029
1030
1031
@repositories.route("/<username>/<repository>/forum/topic/<int:id>")
1032
def repository_forum_topic(username, repository, id):
1033
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1034
if not os.path.exists(server_repo_location):
1035
flask.abort(404)
1036
if not (get_visibility(username, repository) or get_permission_level(
1037
flask.session.get("username"), username,
1038
repository) is not None):
1039
flask.abort(403)
1040
1041
if not os.path.exists(server_repo_location):
1042
return flask.render_template("errors/not-found.html"), 404
1043
1044
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1045
user = User.query.filter_by(username=flask.session.get("username")).first()
1046
relationships = RepoAccess.query.filter_by(repo=repo_data)
1047
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1048
1049
post = Post.query.filter_by(id=id).first()
1050
1051
return flask.render_template(
1052
"repo-topic.html",
1053
username=username,
1054
repository=repository,
1055
repo_data=repo_data,
1056
relationships=relationships,
1057
user_relationship=user_relationship,
1058
post=post,
1059
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1060
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1061
default_branch=repo_data.default_branch
1062
)
1063
1064
1065
@repositories.route("/<username>/<repository>/forum/new", methods=["POST", "GET"])
1066
def repository_forum_new(username, repository):
1067
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1068
if not os.path.exists(server_repo_location):
1069
flask.abort(404)
1070
if not ((flask.session.get("username") and get_visibility(username, repository)) or get_permission_level(
1071
flask.session.get("username"), username,
1072
repository) is not None):
1073
flask.abort(403)
1074
1075
if not os.path.exists(server_repo_location):
1076
return flask.render_template("errors/not-found.html"), 404
1077
1078
repo = git.Repo(server_repo_location)
1079
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1080
user = User.query.filter_by(username=flask.session.get("username")).first()
1081
relationships = RepoAccess.query.filter_by(repo=repo_data)
1082
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1083
1084
post = Post(user, repo_data, None, flask.request.form["subject"],
1085
flask.request.form["message"])
1086
1087
db.session.add(post)
1088
db.session.commit()
1089
1090
return flask.redirect(
1091
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1092
post_id=post.number),
1093
code=303)
1094
1095
1096
@repositories.route("/<username>/<repository>/forum/<int:post_id>")
1097
def repository_forum_thread(username, repository, post_id):
1098
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1099
if not os.path.exists(server_repo_location):
1100
flask.abort(404)
1101
if not (get_visibility(username, repository) or get_permission_level(
1102
flask.session.get("username"), username,
1103
repository) is not None):
1104
flask.abort(403)
1105
1106
if not os.path.exists(server_repo_location):
1107
return flask.render_template("errors/not-found.html"), 404
1108
1109
repo = git.Repo(server_repo_location)
1110
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1111
user = User.query.filter_by(username=flask.session.get("username")).first()
1112
relationships = RepoAccess.query.filter_by(repo=repo_data)
1113
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1114
1115
if user:
1116
max_post_nesting = user.max_post_nesting
1117
else:
1118
max_post_nesting = 2
1119
1120
return flask.render_template(
1121
"repo-forum-thread.html",
1122
username=username,
1123
repository=repository,
1124
repo_data=repo_data,
1125
relationships=relationships,
1126
repo=repo,
1127
Post=Post,
1128
user_relationship=user_relationship,
1129
post_id=post_id,
1130
max_post_nesting=max_post_nesting,
1131
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1132
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1133
parent=Post.query.filter_by(repo=repo_data, number=post_id).first(),
1134
has_permission=not ((not get_permission_level(flask.session.get("username"), username,
1135
repository)) and db.session.get(Post,
1136
f"/{username}/{repository}/{post_id}").owner.username != flask.session.get("username")),
1137
)
1138
1139
1140
@repositories.route("/<username>/<repository>/forum/<int:post_id>/change-state",
1141
methods=["POST"])
1142
def repository_forum_change_state(username, repository, post_id):
1143
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1144
if not os.path.exists(server_repo_location):
1145
flask.abort(404)
1146
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"):
1147
flask.abort(403)
1148
1149
repo = git.Repo(server_repo_location)
1150
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1151
user = User.query.filter_by(username=flask.session.get("username")).first()
1152
relationships = RepoAccess.query.filter_by(repo=repo_data)
1153
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1154
1155
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1156
1157
if not post:
1158
flask.abort(404)
1159
if post.parent:
1160
flask.abort(400)
1161
1162
post.state = int(flask.request.form["new-state"])
1163
1164
db.session.commit()
1165
1166
return flask.redirect(
1167
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1168
post_id=post_id),
1169
code=303)
1170
1171
1172
@repositories.route("/<username>/<repository>/forum/<int:post_id>/reply", methods=["POST"])
1173
def repository_forum_reply(username, repository, post_id):
1174
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1175
if not os.path.exists(server_repo_location):
1176
flask.abort(404)
1177
if not ((flask.session.get("username") and get_visibility(username, repository)) or get_permission_level(
1178
flask.session.get("username"), username,
1179
repository) is not None):
1180
flask.abort(403)
1181
1182
if not os.path.exists(server_repo_location):
1183
return flask.render_template("errors/not-found.html"), 404
1184
1185
repo = git.Repo(server_repo_location)
1186
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1187
user = User.query.filter_by(username=flask.session.get("username")).first()
1188
relationships = RepoAccess.query.filter_by(repo=repo_data)
1189
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1190
if not user:
1191
flask.abort(401)
1192
1193
parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1194
post = Post(user, repo_data, parent, flask.request.form["subject"],
1195
flask.request.form["message"])
1196
1197
db.session.add(post)
1198
post.update_date()
1199
db.session.commit()
1200
1201
return flask.redirect(
1202
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1203
post_id=post_id),
1204
code=303)
1205
1206
1207
@repositories.route("/<username>/<repository>/forum/<int:post_id>/edit", methods=["POST"])
1208
def repository_forum_edit(username, repository, post_id):
1209
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1210
if not os.path.exists(server_repo_location):
1211
flask.abort(404)
1212
if not (get_visibility(username, repository) or get_permission_level(
1213
flask.session.get("username"), username,
1214
repository) is not None):
1215
flask.abort(403)
1216
1217
if not os.path.exists(server_repo_location):
1218
return flask.render_template("errors/not-found.html"), 404
1219
1220
repo = git.Repo(server_repo_location)
1221
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1222
user = User.query.filter_by(username=flask.session.get("username")).first()
1223
relationships = RepoAccess.query.filter_by(repo=repo_data)
1224
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1225
if not user:
1226
flask.abort(401)
1227
post = db.session.get(Post, f"/{username}/{repository}/{post_id}")
1228
if user != post.owner:
1229
flask.abort(403)
1230
1231
post.subject = flask.request.form["subject"]
1232
post.message = flask.request.form["message"]
1233
post.html = markdown.markdown2html(post.message).prettify()
1234
post.update_date()
1235
db.session.commit()
1236
1237
return flask.redirect(
1238
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1239
post_id=post_id),
1240
code=303)
1241
1242
1243
@repositories.route("/<username>/<repository>/forum/<int:post_id>/edit", methods=["GET"])
1244
def repository_forum_edit_form(username, repository, post_id):
1245
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1246
if not os.path.exists(server_repo_location):
1247
flask.abort(404)
1248
if not (get_visibility(username, repository) or get_permission_level(
1249
flask.session.get("username"), username,
1250
repository) is not None):
1251
flask.abort(403)
1252
1253
if not os.path.exists(server_repo_location):
1254
return flask.render_template("errors/not-found.html"), 404
1255
1256
repo = git.Repo(server_repo_location)
1257
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1258
user = User.query.filter_by(username=flask.session.get("username")).first()
1259
relationships = RepoAccess.query.filter_by(repo=repo_data)
1260
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1261
if not user:
1262
flask.abort(401)
1263
post = db.session.get(Post, f"/{username}/{repository}/{post_id}")
1264
if user != post.owner:
1265
flask.abort(403)
1266
1267
return flask.render_template(
1268
"repo-forum-edit.html",
1269
username=username,
1270
repository=repository,
1271
repo_data=repo_data,
1272
relationships=relationships,
1273
repo=repo,
1274
user_relationship=user_relationship,
1275
post=post,
1276
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1277
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1278
default_branch=repo_data.default_branch
1279
)
1280
1281
@repositories.route("/<username>/<repository>/forum/<int:post_id>/voteup",
1282
defaults={"score": 1})
1283
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votedown",
1284
defaults={"score": -1})
1285
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votes", defaults={"score": 0})
1286
def repository_forum_vote(username, repository, post_id, score):
1287
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1288
if not os.path.exists(server_repo_location):
1289
flask.abort(404)
1290
if not (get_visibility(username, repository) or get_permission_level(
1291
flask.session.get("username"), username,
1292
repository) is not None):
1293
flask.abort(403)
1294
1295
if not os.path.exists(server_repo_location):
1296
return flask.render_template("errors/not-found.html"), 404
1297
1298
repo = git.Repo(server_repo_location)
1299
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1300
user = User.query.filter_by(username=flask.session.get("username")).first()
1301
relationships = RepoAccess.query.filter_by(repo=repo_data)
1302
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1303
if not user:
1304
flask.abort(401)
1305
1306
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1307
1308
if score:
1309
old_relationship = PostVote.query.filter_by(user_username=user.username,
1310
post_identifier=post.identifier).first()
1311
if old_relationship:
1312
if score == old_relationship.vote_score:
1313
db.session.delete(old_relationship)
1314
post.vote_sum -= old_relationship.vote_score
1315
else:
1316
post.vote_sum -= old_relationship.vote_score
1317
post.vote_sum += score
1318
old_relationship.vote_score = score
1319
else:
1320
relationship = PostVote(user, post, score)
1321
post.vote_sum += score
1322
db.session.add(relationship)
1323
1324
db.session.commit()
1325
1326
user_vote = PostVote.query.filter_by(user_username=user.username,
1327
post_identifier=post.identifier).first()
1328
response = flask.make_response(
1329
str(post.vote_sum) + " " + str(user_vote.vote_score if user_vote else 0))
1330
response.content_type = "text/plain"
1331
1332
return response
1333
1334
1335
@repositories.route("/<username>/<repository>/forum/<int:post_id>/label", methods=["POST"])
1336
def repository_forum_label(username, repository, post_id):
1337
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1338
if not os.path.exists(server_repo_location):
1339
flask.abort(404)
1340
if not get_permission_level(flask.session.get("username"), username, repository):
1341
flask.abort(403)
1342
1343
repo = git.Repo(server_repo_location)
1344
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1345
user = User.query.filter_by(username=flask.session.get("username")).first()
1346
relationships = RepoAccess.query.filter_by(repo=repo_data)
1347
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1348
1349
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1350
1351
if not post:
1352
flask.abort(404)
1353
if post.parent:
1354
flask.abort(400)
1355
1356
label = db.session.get(Label, flask.request.form["label"])
1357
1358
if PostLabel.query.filter_by(post=post, label=label).first():
1359
return flask.redirect(
1360
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1361
post_id=post_id),
1362
code=303)
1363
1364
post_label = PostLabel(post, label)
1365
db.session.add(post_label)
1366
1367
db.session.commit()
1368
1369
return flask.redirect(
1370
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1371
post_id=post_id),
1372
code=303)
1373
1374
1375
@repositories.route("/<username>/<repository>/forum/<int:post_id>/remove-label")
1376
def repository_forum_remove_label(username, repository, post_id):
1377
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1378
if not os.path.exists(server_repo_location):
1379
flask.abort(404)
1380
if not get_permission_level(flask.session.get("username"), username, repository):
1381
flask.abort(403)
1382
1383
repo = git.Repo(server_repo_location)
1384
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1385
user = User.query.filter_by(username=flask.session.get("username")).first()
1386
relationships = RepoAccess.query.filter_by(repo=repo_data)
1387
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1388
1389
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1390
1391
if not post:
1392
flask.abort(404)
1393
if post.parent:
1394
flask.abort(400)
1395
1396
label = db.session.get(Label, flask.request.args["label"])
1397
1398
post_label = PostLabel.query.filter_by(post=post, label=label).first()
1399
db.session.delete(post_label)
1400
1401
db.session.commit()
1402
1403
return flask.redirect(
1404
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1405
post_id=post_id),
1406
code=303)
1407
1408
1409
@repositories.route("/<username>/<repository>/favourite")
1410
def repository_favourite(username, repository):
1411
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1412
if not os.path.exists(server_repo_location):
1413
flask.abort(404)
1414
if not (get_visibility(username, repository) or get_permission_level(
1415
flask.session.get("username"), username,
1416
repository) is not None):
1417
flask.abort(403)
1418
1419
if not os.path.exists(server_repo_location):
1420
return flask.render_template("errors/not-found.html"), 404
1421
1422
repo = git.Repo(server_repo_location)
1423
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1424
user = User.query.filter_by(username=flask.session.get("username")).first()
1425
relationships = RepoAccess.query.filter_by(repo=repo_data)
1426
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1427
if not user:
1428
flask.abort(401)
1429
1430
old_relationship = RepoFavourite.query.filter_by(user_username=user.username,
1431
repo_route=repo_data.route).first()
1432
if old_relationship:
1433
db.session.delete(old_relationship)
1434
else:
1435
relationship = RepoFavourite(user, repo_data)
1436
db.session.add(relationship)
1437
1438
db.session.commit()
1439
1440
return flask.redirect(flask.url_for("favourites"), code=303)
1441
1442
1443
@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"])
1444
def repository_users(username, repository):
1445
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1446
if not os.path.exists(server_repo_location):
1447
flask.abort(404)
1448
if not (get_visibility(username, repository) or get_permission_level(
1449
flask.session.get("username"), username,
1450
repository) is not None):
1451
flask.abort(403)
1452
1453
if not os.path.exists(server_repo_location):
1454
return flask.render_template("errors/not-found.html"), 404
1455
1456
repo = git.Repo(server_repo_location)
1457
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1458
user = User.query.filter_by(username=flask.session.get("username")).first()
1459
relationships = RepoAccess.query.filter_by(repo=repo_data)
1460
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1461
1462
if flask.request.method == "GET":
1463
return flask.render_template(
1464
"repo-users.html",
1465
username=username,
1466
repository=repository,
1467
repo_data=repo_data,
1468
relationships=relationships,
1469
repo=repo,
1470
user_relationship=user_relationship,
1471
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1472
is_favourite=get_favourite(flask.session.get("username"), username, repository)
1473
)
1474
else:
1475
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1476
flask.abort(401)
1477
1478
if flask.request.form.get("new-username"):
1479
# Create new relationship
1480
new_user = User.query.filter_by(
1481
username=flask.request.form.get("new-username")).first()
1482
relationship = RepoAccess(new_user, repo_data, flask.request.form.get("new-level"))
1483
db.session.add(relationship)
1484
db.session.commit()
1485
if flask.request.form.get("update-username"):
1486
# Create new relationship
1487
updated_user = User.query.filter_by(
1488
username=flask.request.form.get("update-username")).first()
1489
relationship = RepoAccess.query.filter_by(repo=repo_data, user=updated_user).first()
1490
if flask.request.form.get("update-level") == -1:
1491
relationship.delete()
1492
else:
1493
relationship.access_level = flask.request.form.get("update-level")
1494
db.session.commit()
1495
1496
return flask.redirect(
1497
app.url_for(".repository_users", username=username, repository=repository))
1498
1499
1500
@repositories.route("/<username>/<repository>/branches/")
1501
def repository_branches(username, repository):
1502
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1503
if not os.path.exists(server_repo_location):
1504
flask.abort(404)
1505
if not (get_visibility(username, repository) or get_permission_level(
1506
flask.session.get("username"), username,
1507
repository) is not None):
1508
flask.abort(403)
1509
1510
if not os.path.exists(server_repo_location):
1511
return flask.render_template("errors/not-found.html"), 404
1512
1513
repo = git.Repo(server_repo_location)
1514
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1515
1516
return flask.render_template(
1517
"repo-branches.html",
1518
username=username,
1519
repository=repository,
1520
repo_data=repo_data,
1521
repo=repo,
1522
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1523
is_favourite=get_favourite(flask.session.get("username"), username, repository)
1524
)
1525
1526
1527
@repositories.route("/<username>/<repository>/log/", defaults={"branch": None})
1528
@repositories.route("/<username>/<repository>/log/<branch>/")
1529
def repository_log(username, repository, branch):
1530
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1531
if not os.path.exists(server_repo_location):
1532
flask.abort(404)
1533
if not (get_visibility(username, repository) or get_permission_level(
1534
flask.session.get("username"), username,
1535
repository) is not None):
1536
flask.abort(403)
1537
1538
if not os.path.exists(server_repo_location):
1539
return flask.render_template("errors/not-found.html"), 404
1540
1541
repo = git.Repo(server_repo_location)
1542
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1543
if not repo_data.default_branch:
1544
if repo.heads:
1545
repo_data.default_branch = repo.heads[0].name
1546
else:
1547
return flask.render_template("empty.html",
1548
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
1549
if not branch:
1550
branch = repo_data.default_branch
1551
return flask.redirect(f"./{branch}", code=302)
1552
1553
if branch.startswith("tag:"):
1554
ref = f"tags/{branch[4:]}"
1555
elif branch.startswith("~"):
1556
ref = branch[1:]
1557
else:
1558
ref = f"heads/{branch}"
1559
1560
ref = ref.replace("~", "/") # encode slashes for URL support
1561
1562
try:
1563
repo.git.checkout("-f", ref)
1564
except git.exc.GitCommandError:
1565
return flask.render_template("errors/not-found.html"), 404
1566
1567
branches = repo.heads
1568
1569
all_refs = []
1570
for ref in repo.heads:
1571
all_refs.append((ref, "head"))
1572
for ref in repo.tags:
1573
all_refs.append((ref, "tag"))
1574
1575
commit_list = [f"/{username}/{repository}/{sha}" for sha in
1576
git_command(server_repo_location, None, "log",
1577
"--format='%H'").decode().split("\n")]
1578
1579
commits = Commit.query.filter(Commit.identifier.in_(commit_list)).order_by(Commit.author_date.desc())
1580
page_number = flask.request.args.get("page", 1, type=int)
1581
if flask.session.get("username"):
1582
default_page_length = db.session.get(User, flask.session.get("username")).default_page_length
1583
else:
1584
default_page_length = 16
1585
page_length = flask.request.args.get("per_page", default_page_length, type=int)
1586
page_listing = db.paginate(commits, page=page_number, per_page=page_length)
1587
1588
if page_listing.has_next:
1589
next_page = page_listing.next_num
1590
else:
1591
next_page = None
1592
1593
if page_listing.has_prev:
1594
prev_page = page_listing.prev_num
1595
else:
1596
prev_page = None
1597
1598
return flask.render_template(
1599
"repo-log.html",
1600
username=username,
1601
repository=repository,
1602
branches=all_refs,
1603
current=branch,
1604
repo_data=repo_data,
1605
repo=repo,
1606
commits=page_listing,
1607
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1608
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1609
page_number=page_number,
1610
page_length=page_length,
1611
next_page=next_page,
1612
prev_page=prev_page,
1613
num_pages=page_listing.pages
1614
)
1615
1616
1617
@repositories.route("/<username>/<repository>/prs/", methods=["GET", "POST"])
1618
def repository_prs(username, repository):
1619
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1620
if not os.path.exists(server_repo_location):
1621
flask.abort(404)
1622
if not (get_visibility(username, repository) or get_permission_level(
1623
flask.session.get("username"), username,
1624
repository) is not None):
1625
flask.abort(403)
1626
1627
if not os.path.exists(server_repo_location):
1628
return flask.render_template("errors/not-found.html"), 404
1629
1630
if flask.request.method == "GET":
1631
repo = git.Repo(server_repo_location)
1632
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1633
user = User.query.filter_by(username=flask.session.get("username")).first()
1634
1635
return flask.render_template(
1636
"repo-prs.html",
1637
username=username,
1638
repository=repository,
1639
repo_data=repo_data,
1640
repo=repo,
1641
PullRequest=PullRequest,
1642
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1643
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1644
default_branch=repo_data.default_branch,
1645
branches=repo.branches
1646
)
1647
1648
elif "id" not in flask.request.form:
1649
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1650
head = flask.request.form.get("head")
1651
head_route = flask.request.form.get("headroute")
1652
base = flask.request.form.get("base")
1653
1654
if not head and base and head_route:
1655
return flask.redirect(".", 400)
1656
1657
head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/")))
1658
base_repo = git.Repo(server_repo_location)
1659
1660
if head not in head_repo.branches or base not in base_repo.branches:
1661
flask.flash(Markup(
1662
"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Bad branch name")),
1663
category="error")
1664
return flask.redirect(".", 303)
1665
1666
head_data = db.session.get(Repo, head_route)
1667
if not head_data.visibility:
1668
flask.flash(Markup(
1669
"<iconify-icon icon='mdi:error'></iconify-icon>" + _(
1670
"Head can't be restricted")),
1671
category="error")
1672
return flask.redirect(".", 303)
1673
1674
pull_request = PullRequest(head_data, head, repo_data, base,
1675
db.session.get(User, flask.session["username"]))
1676
1677
db.session.add(pull_request)
1678
db.session.commit()
1679
1680
# Create the notification
1681
notification = Notification({"type": "pr", "head": pull_request.head.route, "base": pull_request.base.route, "pr": pull_request.id})
1682
db.session.add(notification)
1683
db.session.commit()
1684
1685
# Send a notification to all users who have enabled PR notifications for this repo
1686
for relationship in RepoFavourite.query.filter_by(repo_route=pull_request.base.route, notify_pr=True).all():
1687
user = relationship.user
1688
user_notification = UserNotification(user, notification, 1)
1689
db.session.add(user_notification)
1690
db.session.commit()
1691
celery_tasks.send_notification.apply_async(args=[user_notification.id])
1692
1693
return flask.redirect(".", 303)
1694
else:
1695
id = flask.request.form.get("id")
1696
pull_request = db.session.get(PullRequest, id)
1697
1698
if not pull_request:
1699
flask.abort(404)
1700
1701
if not (get_visibility(username, repository) or get_permission_level(
1702
flask.session.get("username"), username,
1703
repository) >= 1 or pull_request.owner.username == flask.session.get("username")):
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
1711
if pull_request:
1712
pull_request.resolves_list = flask.request.form.get("resolves")
1713
db.session.commit()
1714
1715
return flask.redirect(".", 303)
1716
1717
1718
@repositories.route("/<username>/<repository>/prs/merge", methods=["POST"])
1719
def repository_prs_merge(username, repository):
1720
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1721
if not os.path.exists(server_repo_location):
1722
flask.abort(404)
1723
if not (get_visibility(username, repository) or get_permission_level(
1724
flask.session.get("username"), username,
1725
repository) is not None):
1726
flask.abort(403)
1727
1728
if not get_permission_level(flask.session.get("username"), username, repository):
1729
flask.abort(401)
1730
1731
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1732
repo = git.Repo(server_repo_location)
1733
id = flask.request.form.get("id")
1734
1735
pull_request = db.session.get(PullRequest, id)
1736
1737
if pull_request:
1738
result = celery_tasks.merge_heads.delay(
1739
pull_request.head_route,
1740
pull_request.head_branch,
1741
pull_request.base_route,
1742
pull_request.base_branch,
1743
pull_request.id,
1744
simulate=True
1745
)
1746
task_result = worker.AsyncResult(result.id)
1747
1748
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303) # should be 202 Accepted but we must use a redirect
1749
# db.session.delete(pull_request)
1750
# db.session.commit()
1751
else:
1752
flask.abort(400)
1753
1754
1755
@repositories.route("/<username>/<repository>/prs/<int:id>/merge")
1756
def repository_prs_merge_stage_two(username, repository, id):
1757
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1758
if not os.path.exists(server_repo_location):
1759
flask.abort(404)
1760
if not (get_visibility(username, repository) or get_permission_level(
1761
flask.session.get("username"), username,
1762
repository) is not None):
1763
flask.abort(403)
1764
1765
if not get_permission_level(flask.session.get("username"), username, repository):
1766
flask.abort(401)
1767
1768
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1769
repo = git.Repo(server_repo_location)
1770
1771
pull_request = db.session.get(PullRequest, id)
1772
1773
if pull_request:
1774
result = celery_tasks.merge_heads.delay(
1775
pull_request.head_route,
1776
pull_request.head_branch,
1777
pull_request.base_route,
1778
pull_request.base_branch,
1779
pull_request.id,
1780
simulate=False
1781
)
1782
task_result = worker.AsyncResult(result.id)
1783
1784
db.session.commit()
1785
1786
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
1787
# db.session.delete(pull_request)
1788
else:
1789
flask.abort(400)
1790
1791
1792
@app.route("/task/<task_id>")
1793
def task_monitor(task_id):
1794
task_result = worker.AsyncResult(task_id)
1795
1796
if flask.request.args.get("partial"):
1797
# htmx partial update
1798
return render_block("task-monitor.html", "content", result=task_result, query_string=flask.request.query_string.decode(), delay=1000)
1799
1800
# Since most tasks finish rather quickly, the initial delay is faster, so it doesn't wait for too long
1801
return flask.render_template("task-monitor.html", result=task_result, query_string=flask.request.query_string.decode(), delay=125)
1802
1803
1804
@repositories.route("/<username>/<repository>/prs/delete", methods=["POST"])
1805
def repository_prs_delete(username, repository):
1806
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1807
if not os.path.exists(server_repo_location):
1808
flask.abort(404)
1809
if not (get_visibility(username, repository) or get_permission_level(
1810
flask.session.get("username"), username,
1811
repository) is not None):
1812
flask.abort(403)
1813
1814
if not get_permission_level(flask.session.get("username"), username, repository):
1815
flask.abort(401)
1816
1817
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1818
repo = git.Repo(server_repo_location)
1819
id = flask.request.form.get("id")
1820
1821
pull_request = db.session.get(PullRequest, id)
1822
1823
if pull_request:
1824
pull_request.state = 2
1825
db.session.commit()
1826
1827
return flask.redirect(".", 303)
1828
1829
1830
@repositories.route("/<username>/<repository>/settings/")
1831
def repository_settings(username, repository):
1832
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1833
flask.abort(401)
1834
1835
repo = git.Repo(os.path.join(config.REPOS_PATH, username, repository))
1836
1837
site_link = Markup(f"<code>http{'s' if config.suggest_https else ''}://{username}.{config.BASE_DOMAIN}/{repository}</code>")
1838
primary_site_link = Markup(f"<code>http{'s' if config.suggest_https else ''}://{username}.{config.BASE_DOMAIN}/</code>")
1839
1840
return flask.render_template("repo-settings.html", username=username, repository=repository,
1841
repo_data=db.session.get(Repo, f"/{username}/{repository}"),
1842
branches=[branch.name for branch in repo.branches],
1843
site_link=site_link, primary_site_link=primary_site_link,
1844
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1845
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1846
)
1847
1848
1849
@repositories.route("/<username>/<repository>/settings/", methods=["POST"])
1850
def repository_settings_post(username, repository):
1851
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1852
flask.abort(401)
1853
1854
repo = db.session.get(Repo, f"/{username}/{repository}")
1855
1856
repo.visibility = flask.request.form.get("visibility", type=int)
1857
repo.info = flask.request.form.get("description")
1858
repo.default_branch = flask.request.form.get("default_branch")
1859
repo.url = flask.request.form.get("url")
1860
1861
# Update site settings
1862
had_site = repo.has_site
1863
old_branch = repo.site_branch
1864
if flask.request.form.get("site_branch"):
1865
repo.site_branch = flask.request.form.get("site_branch")
1866
if flask.request.form.get("primary_site"):
1867
if had_site != 2:
1868
# Remove primary site from other repos
1869
for other_repo in Repo.query.filter_by(owner=repo.owner, has_site=2):
1870
other_repo.has_site = 1 # switch it to a regular site
1871
flask.flash(Markup(
1872
_("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(
1873
repository=other_repo.route
1874
)), category="warning")
1875
repo.has_site = 2
1876
else:
1877
repo.has_site = 1
1878
else:
1879
repo.site_branch = None
1880
repo.has_site = 0
1881
1882
db.session.commit()
1883
1884
if not (had_site, old_branch) == (repo.has_site, repo.site_branch):
1885
# Deploy the newly activated site
1886
result = celery_tasks.copy_site.delay(repo.route)
1887
1888
if had_site and not repo.has_site:
1889
# Remove the site
1890
result = celery_tasks.delete_site.delay(repo.route)
1891
1892
if repo.has_site == 2 or (had_site == 2 and had_site != repo.has_site):
1893
# Deploy all other sites which were destroyed by the primary site
1894
for other_repo in Repo.query.filter_by(owner=repo.owner, has_site=1):
1895
result = celery_tasks.copy_site.delay(other_repo.route)
1896
1897
return flask.redirect(f"/{username}/{repository}/settings", 303)
1898
1899
1900
@repositories.route("/<username>/<repository>/settings/add-label", methods=["POST"])
1901
def repository_settings_add_label(username, repository):
1902
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1903
flask.abort(401)
1904
1905
repo_data = db.session.get(Repo, f"/{username}/{repository}")
1906
1907
label = Label(repo_data, flask.request.form.get("label"), flask.request.form.get("colour"))
1908
db.session.add(label)
1909
db.session.commit()
1910
1911
return flask.redirect(f"/{username}/{repository}/settings", 303)
1912
1913
1914
@repositories.route("/<username>/<repository>/settings/delete-label", methods=["POST"])
1915
def repository_settings_delete_label(username, repository):
1916
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1917
flask.abort(401)
1918
1919
repo_data = db.session.get(Repo, f"/{username}/{repository}")
1920
1921
label = db.session.get(Label, flask.request.form.get("id"))
1922
1923
db.session.delete(label)
1924
db.session.commit()
1925
1926
return flask.redirect(f"/{username}/{repository}/settings", 303)
1927
1928
1929
@repositories.route("/<username>/<repository>/settings/edit-label", methods=["POST"])
1930
def repository_settings_edit_label(username, repository):
1931
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1932
flask.abort(401)
1933
1934
repo_data = db.session.get(Repo, f"/{username}/{repository}")
1935
1936
label = db.session.get(Label, flask.request.form.get("id"))
1937
1938
label.name = flask.request.form.get("label")
1939
label.colour_hex = flask.request.form.get("colour")
1940
1941
db.session.commit()
1942
1943
return flask.redirect(f"/{username}/{repository}/settings", 303)
1944
1945
1946
@repositories.route("/<username>/<repository>/settings/delete", methods=["POST"])
1947
def repository_settings_delete(username, repository):
1948
if username != flask.session.get("username"):
1949
flask.abort(401)
1950
1951
repo = db.session.get(Repo, f"/{username}/{repository}")
1952
1953
if not repo:
1954
flask.abort(404)
1955
1956
user = db.session.get(User, flask.session.get("username"))
1957
1958
if not bcrypt.check_password_hash(user.password_hashed, flask.request.form.get("password")):
1959
flask.flash(_("Incorrect password"), category="error")
1960
flask.abort(401)
1961
1962
if repo.has_site:
1963
celery_tasks.delete_site.delay(repo.route)
1964
1965
db.session.delete(repo)
1966
db.session.commit()
1967
1968
shutil.rmtree(os.path.join(config.REPOS_PATH, username, repository))
1969
1970
return flask.redirect(f"/{username}", 303)
1971
1972
1973
@app.errorhandler(404)
1974
def e404(error):
1975
return flask.render_template("errors/not-found.html"), 404
1976
1977
1978
@app.errorhandler(401)
1979
def e401(error):
1980
return flask.render_template("errors/unauthorised.html"), 401
1981
1982
1983
@app.errorhandler(403)
1984
def e403(error):
1985
return flask.render_template("errors/forbidden.html"), 403
1986
1987
1988
@app.errorhandler(418)
1989
def e418(error):
1990
return flask.render_template("errors/teapot.html"), 418
1991
1992
1993
@app.errorhandler(405)
1994
def e405(error):
1995
return flask.render_template("errors/method-not-allowed.html"), 405
1996
1997
1998
@app.errorhandler(500)
1999
def e500(error):
2000
return flask.render_template("errors/server-error.html"), 500
2001
2002
2003
@app.errorhandler(400)
2004
def e400(error):
2005
return flask.render_template("errors/bad-request.html"), 400
2006
2007
2008
@app.errorhandler(410)
2009
def e410(error):
2010
return flask.render_template("errors/gone.html"), 410
2011
2012
2013
@app.errorhandler(415)
2014
def e415(error):
2015
return flask.render_template("errors/media-type.html"), 415
2016
2017
2018
if __name__ == "__main__":
2019
app.run(debug=True, port=8080, host="0.0.0.0")
2020
2021
app.register_blueprint(repositories)
2022