app.py
Python script, Unicode text, UTF-8 text executable
1__version__ = "0.1.2" 2 3import os 4import shutil 5import random 6import subprocess 7import platform 8import git 9import mimetypes 10import magic 11import flask 12import cairosvg 13import celery 14import shlex 15from functools import wraps 16from datetime import datetime 17from enum import Enum 18from cairosvg import svg2png 19from flask_sqlalchemy import SQLAlchemy 20from flask_bcrypt import Bcrypt 21from markupsafe import escape, Markup 22from flask_migrate import Migrate 23from PIL import Image 24from flask_httpauth import HTTPBasicAuth 25import config 26from flask_babel import Babel, gettext, ngettext, force_locale 27 28_ = gettext 29n_ = gettext 30 31app = flask.Flask(__name__) 32app.config.from_mapping( 33CELERY=dict( 34broker_url=config.REDIS_URI, 35result_backend=config.REDIS_URI, 36task_ignore_result=True, 37), 38) 39 40auth = HTTPBasicAuth() 41 42app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI 43app.config["SECRET_KEY"] = config.DB_PASSWORD 44app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 45app.config["BABEL_TRANSLATION_DIRECTORIES"] = "i18n" 46app.config["MAX_CONTENT_LENGTH"] = config.MAX_PAYLOAD_SIZE 47 48db = SQLAlchemy(app) 49bcrypt = Bcrypt(app) 50migrate = Migrate(app, db) 51 52from models import * 53from misc_utils import * 54 55import git_http 56import jinja_utils 57import celery_tasks 58from celery import Celery, Task 59import celery_integration 60import pathlib 61 62babel = Babel(app) 63 64 65def get_locale(): 66if flask.request.cookies.get("language"): 67return flask.request.cookies.get("language") 68return flask.request.accept_languages.best_match(config.available_locales) 69 70 71babel.init_app(app, locale_selector=get_locale) 72 73with app.app_context(): 74locale_names = {} 75for language in config.available_locales: 76with force_locale(language): 77# NOTE: Translate this to the language's name in that language, for example in French you would use français 78locale_names[language] = gettext("English") 79 80worker = celery_integration.init_celery_app(app) 81 82repositories = flask.Blueprint("repository", __name__, template_folder="templates/repository/") 83 84app.jinja_env.add_extension("jinja2.ext.do") 85app.jinja_env.add_extension("jinja2.ext.loopcontrols") 86app.jinja_env.add_extension("jinja2.ext.debug") 87 88 89@app.context_processor 90def default(): 91username = flask.session.get("username") 92 93user_object = User.query.filter_by(username=username).first() 94 95return { 96"logged_in_user": username, 97"user_object": user_object, 98"Notification": Notification, 99"unread": UserNotification.query.filter_by(user_username=username).filter( 100UserNotification.attention_level > 0).count(), 101"config": config, 102"Markup": Markup, 103"locale_names": locale_names, 104} 105 106 107@app.route("/") 108def main(): 109if flask.session.get("username"): 110return flask.render_template("home.html") 111else: 112return flask.render_template("no-home.html") 113 114 115@app.route("/userstyle") 116def userstyle(): 117if flask.session.get("username") and os.path.exists( 118os.path.join(config.REPOS_PATH, flask.session.get("username"), ".config", 119"theme.css")): 120return flask.send_from_directory( 121os.path.join(config.REPOS_PATH, flask.session.get("username"), ".config"), 122"theme.css") 123else: 124return flask.Response("", mimetype="text/css") 125 126 127@app.route("/about/") 128def about(): 129return flask.render_template("about.html", platform=platform, version=__version__) 130 131 132@app.route("/language", methods=["POST"]) 133def set_locale(): 134response = flask.redirect(flask.request.referrer if flask.request.referrer else "/", 135code=303) 136if not flask.request.form.get("language"): 137response.delete_cookie("language") 138else: 139response.set_cookie("language", flask.request.form.get("language")) 140 141return response 142 143 144@app.route("/cookie-dismiss") 145def dismiss_banner(): 146response = flask.redirect(flask.request.referrer if flask.request.referrer else "/", 147code=303) 148response.set_cookie("cookie-banner", "1") 149return response 150 151 152@app.route("/help/") 153def help_index(): 154return flask.render_template("help.html", faqs=config.faqs) 155 156 157@app.route("/settings/", methods=["GET", "POST"]) 158def settings(): 159if not flask.session.get("username"): 160flask.abort(401) 161if flask.request.method == "GET": 162user = User.query.filter_by(username=flask.session.get("username")).first() 163 164return flask.render_template("user-settings.html", user=user) 165else: 166user = User.query.filter_by(username=flask.session.get("username")).first() 167 168user.display_name = flask.request.form["displayname"] 169user.URL = flask.request.form["url"] 170user.company = flask.request.form["company"] 171user.company_URL = flask.request.form["companyurl"] 172user.email = flask.request.form.get("email") if flask.request.form.get( 173"email") else None 174user.location = flask.request.form["location"] 175user.show_mail = True if flask.request.form.get("showmail") else False 176user.bio = flask.request.form.get("bio") 177 178db.session.commit() 179 180flask.flash( 181Markup("<iconify-icon icon='mdi:check'></iconify-icon>" + _("Settings saved")), 182category="success") 183return flask.redirect(f"/{flask.session.get('username')}", code=303) 184 185 186@app.route("/favourites/", methods=["GET", "POST"]) 187def favourites(): 188if not flask.session.get("username"): 189flask.abort(401) 190if flask.request.method == "GET": 191relationships = RepoFavourite.query.filter_by( 192user_username=flask.session.get("username")) 193 194return flask.render_template("favourites.html", favourites=relationships) 195 196 197@app.route("/favourites/<int:id>", methods=["POST"]) 198def favourite_edit(id): 199if not flask.session.get("username"): 200flask.abort(401) 201favourite = db.session.get(RepoFavourite, id) 202if favourite.user_username != flask.session.get("username"): 203flask.abort(403) 204data = flask.request.form 205print(data) 206favourite.notify_commit = js_to_bool(data.get("commit")) 207favourite.notify_forum = js_to_bool(data.get("forum")) 208favourite.notify_pr = js_to_bool(data.get("pull_request")) 209favourite.notify_admin = js_to_bool(data.get("administrative")) 210print(favourite.notify_commit, favourite.notify_forum, favourite.notify_pr, 211favourite.notify_admin) 212db.session.commit() 213return flask.render_template_string( 214""" 215<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"> 216<td><a href="{{ favourite.repo.route }}">{{ favourite.repo.owner.username }}/{{ favourite.repo.name }}</a></td> 217<td style="text-align: center;"><input type="checkbox" name="commit" id="commit-{{ favourite.id }}" value="true" {% if favourite.notify_commit %}checked{% endif %}></td> 218<td style="text-align: center;"><input type="checkbox" name="forum" id="forum-{{ favourite.id }}" value="true" {% if favourite.notify_forum %}checked{% endif %}></td> 219<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> 220<td style="text-align: center;"><input type="checkbox" name="administrative" id="administrative-{{ favourite.id }}" value="true" {% if favourite.notify_admin %}checked{% endif %}></td> 221</tr> 222""", 223favourite=favourite 224) 225 226 227@app.route("/notifications/", methods=["GET", "POST"]) 228def notifications(): 229if not flask.session.get("username"): 230flask.abort(401) 231if flask.request.method == "GET": 232return flask.render_template("notifications.html", 233notifications=UserNotification.query.filter_by( 234user_username=flask.session.get("username") 235).order_by(UserNotification.id.desc()), 236db=db, Commit=Commit 237) 238 239 240@app.route("/notifications/<int:notification_id>/read", methods=["POST"]) 241def mark_read(notification_id): 242if not flask.session.get("username"): 243flask.abort(401) 244notification = UserNotification.query.filter_by(id=notification_id).first() 245if notification.user_username != flask.session.get("username"): 246flask.abort(403) 247notification.mark_read() 248db.session.commit() 249return flask.render_template_string( 250"<button hx-post='/notifications/{{ notification.id }}/unread' hx-swap='outerHTML'>Mark as unread</button>", 251notification=notification), 200 252 253 254@app.route("/notifications/<int:notification_id>/unread", methods=["POST"]) 255def mark_unread(notification_id): 256if not flask.session.get("username"): 257flask.abort(401) 258notification = UserNotification.query.filter_by(id=notification_id).first() 259if notification.user_username != flask.session.get("username"): 260flask.abort(403) 261notification.mark_unread() 262db.session.commit() 263return flask.render_template_string( 264"<button hx-post='/notifications/{{ notification.id }}/read' hx-swap='outerHTML'>Mark as read</button>", 265notification=notification), 200 266 267 268@app.route("/notifications/mark-all-read", methods=["POST"]) 269def mark_all_read(): 270if not flask.session.get("username"): 271flask.abort(401) 272 273notifications = UserNotification.query.filter_by( 274user_username=flask.session.get("username")) 275for notification in notifications: 276notification.mark_read() 277db.session.commit() 278return flask.redirect("/notifications/", code=303) 279 280 281@app.route("/accounts/", methods=["GET", "POST"]) 282def login(): 283if flask.request.method == "GET": 284return flask.render_template("login.html") 285else: 286if "login" in flask.request.form: 287username = flask.request.form["username"] 288password = flask.request.form["password"] 289 290user = User.query.filter_by(username=username).first() 291 292if user and bcrypt.check_password_hash(user.password_hashed, password): 293flask.session["username"] = user.username 294flask.flash( 295Markup("<iconify-icon icon='mdi:account'></iconify-icon>" + _( 296"Successfully logged in as {username}").format( 297username=username)), 298category="success") 299return flask.redirect("/", code=303) 300elif not user: 301flask.flash(Markup( 302"<iconify-icon icon='mdi:account-question'></iconify-icon>" + _( 303"User not found")), 304category="alert") 305return flask.render_template("login.html") 306else: 307flask.flash(Markup( 308"<iconify-icon icon='mdi:account-question'></iconify-icon>" + _( 309"Invalid password")), 310category="error") 311return flask.render_template("login.html") 312if "signup" in flask.request.form: 313username = flask.request.form["username"] 314password = flask.request.form["password"] 315password2 = flask.request.form["password2"] 316email = flask.request.form.get("email") 317email2 = flask.request.form.get("email2") # repeat email is a honeypot 318name = flask.request.form.get("name") 319 320if not only_chars(username, 321"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): 322flask.flash(Markup( 323_("Usernames may only contain Latin alphabet, numbers, '-' and '_'")), 324category="error") 325return flask.render_template("login.html") 326 327if username in config.RESERVED_NAMES: 328flask.flash( 329Markup( 330"<iconify-icon icon='mdi:account-error'></iconify-icon>" + _( 331"Sorry, {username} is a system path").format( 332username=username)), 333category="error") 334return flask.render_template("login.html") 335 336user_check = User.query.filter_by(username=username).first() 337if user_check or email2: # make the honeypot look like a normal error 338flask.flash( 339Markup( 340"<iconify-icon icon='mdi:account-error'></iconify-icon>" + _( 341"The username {username} is taken").format( 342username=username)), 343category="error") 344return flask.render_template("login.html") 345 346if password2 != password: 347flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>" + _( 348"Make sure the passwords match")), 349category="error") 350return flask.render_template("login.html") 351 352user = User(username, password, email, name) 353db.session.add(user) 354db.session.commit() 355flask.session["username"] = user.username 356flask.flash(Markup( 357"<iconify-icon icon='mdi:account'></iconify-icon>" + _( 358"Successfully created and logged in as {username}").format( 359username=username)), 360category="success") 361 362notification = Notification({"type": "welcome"}) 363db.session.add(notification) 364db.session.commit() 365 366result = celery_tasks.send_notification.delay(notification.id, [username], 1) 367 368return flask.redirect("/", code=303) 369 370 371@app.route("/newrepo/", methods=["GET", "POST"]) 372def new_repo(): 373if not flask.session.get("username"): 374flask.abort(401) 375if flask.request.method == "GET": 376return flask.render_template("new-repo.html") 377else: 378name = flask.request.form["name"] 379visibility = int(flask.request.form["visibility"]) 380 381if not only_chars(name, 382"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): 383flask.flash(Markup( 384"<iconify-icon icon='mdi:error'></iconify-icon>" + _( 385"Repository names may only contain Latin alphabet, numbers, '-' and '_'")), 386category="error") 387return flask.render_template("new-repo.html") 388 389user = User.query.filter_by(username=flask.session.get("username")).first() 390 391repo = Repo(user, name, visibility) 392db.session.add(repo) 393db.session.commit() 394 395flask.flash(Markup(_("Successfully created repository {name}").format(name=name)), 396category="success") 397return flask.redirect(repo.route, code=303) 398 399 400@app.route("/logout") 401def logout(): 402flask.session.clear() 403flask.flash(Markup( 404"<iconify-icon icon='mdi:account'></iconify-icon>" + _("Successfully logged out")), 405category="info") 406return flask.redirect("/", code=303) 407 408 409@app.route("/<username>/", methods=["GET", "POST"]) 410def user_profile(username): 411old_relationship = UserFollow.query.filter_by( 412follower_username=flask.session.get("username"), 413followed_username=username).first() 414if flask.request.method == "GET": 415user = User.query.filter_by(username=username).first() 416match flask.request.args.get("action"): 417case "repositories": 418repos = Repo.query.filter_by(owner_name=username, visibility=2) 419return flask.render_template("user-profile-repositories.html", user=user, 420repos=repos, 421relationship=old_relationship) 422case "followers": 423return flask.render_template("user-profile-followers.html", user=user, 424relationship=old_relationship) 425case "follows": 426return flask.render_template("user-profile-follows.html", user=user, 427relationship=old_relationship) 428case _: 429return flask.render_template("user-profile-overview.html", user=user, 430relationship=old_relationship) 431 432elif flask.request.method == "POST": 433match flask.request.args.get("action"): 434case "follow": 435if username == flask.session.get("username"): 436flask.abort(403) 437if old_relationship: 438db.session.delete(old_relationship) 439else: 440relationship = UserFollow( 441flask.session.get("username"), 442username 443) 444db.session.add(relationship) 445db.session.commit() 446 447user = db.session.get(User, username) 448author = db.session.get(User, flask.session.get("username")) 449notification = Notification({"type": "update", "version": "0.0.0"}) 450db.session.add(notification) 451db.session.commit() 452 453result = celery_tasks.send_notification.delay(notification.id, [username], 4541) 455 456db.session.commit() 457return flask.redirect("?", code=303) 458 459 460@app.route("/<username>/<repository>/") 461def repository_index(username, repository): 462return flask.redirect("./tree", code=302) 463 464 465@app.route("/info/<username>/avatar") 466def user_avatar(username): 467serverUserdataLocation = os.path.join(config.USERDATA_PATH, username) 468 469if not os.path.exists(serverUserdataLocation): 470return flask.render_template("not-found.html"), 404 471 472return flask.send_from_directory(serverUserdataLocation, "avatar.png") 473 474 475@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>") 476def repository_raw(username, repository, branch, subpath): 477server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 478if not os.path.exists(server_repo_location): 479app.logger.error(f"Cannot load {server_repo_location}") 480flask.abort(404) 481if not (get_visibility(username, repository) or get_permission_level( 482flask.session.get("username"), username, 483repository) is not None): 484flask.abort(403) 485 486app.logger.info(f"Loading {server_repo_location}") 487 488if not os.path.exists(server_repo_location): 489app.logger.error(f"Cannot load {server_repo_location}") 490return flask.render_template("not-found.html"), 404 491 492repo = git.Repo(server_repo_location) 493repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 494if not repo_data.default_branch: 495if repo.heads: 496repo_data.default_branch = repo.heads[0].name 497else: 498return flask.render_template("empty.html", 499remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 500if not branch: 501branch = repo_data.default_branch 502return flask.redirect(f"./{branch}", code=302) 503 504if branch.startswith("tag:"): 505ref = f"tags/{branch[4:]}" 506elif branch.startswith("~"): 507ref = branch[1:] 508else: 509ref = f"heads/{branch}" 510 511ref = ref.replace("~", "/") # encode slashes for URL support 512 513try: 514repo.git.checkout("-f", ref) 515except git.exc.GitCommandError: 516return flask.render_template("not-found.html"), 404 517 518return flask.send_from_directory(config.REPOS_PATH, 519os.path.join(username, repository, subpath)) 520 521 522@repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""}) 523@repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""}) 524@repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>") 525def repository_tree(username, repository, branch, subpath): 526server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 527if not os.path.exists(server_repo_location): 528app.logger.error(f"Cannot load {server_repo_location}") 529flask.abort(404) 530if not (get_visibility(username, repository) or get_permission_level( 531flask.session.get("username"), username, 532repository) is not None): 533flask.abort(403) 534 535app.logger.info(f"Loading {server_repo_location}") 536 537repo = git.Repo(server_repo_location) 538repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 539if not repo_data.default_branch: 540if repo.heads: 541repo_data.default_branch = repo.heads[0].name 542else: 543return flask.render_template("empty.html", 544remote=f"{config.www_protocol}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 545if not branch: 546branch = repo_data.default_branch 547return flask.redirect(f"./{branch}", code=302) 548 549if branch.startswith("tag:"): 550ref = f"tags/{branch[4:]}" 551elif branch.startswith("~"): 552ref = branch[1:] 553else: 554ref = f"heads/{branch}" 555 556ref = ref.replace("~", "/") # encode slashes for URL support 557 558try: 559repo.git.checkout("-f", ref) 560except git.exc.GitCommandError: 561return flask.render_template("not-found.html"), 404 562 563branches = repo.heads 564 565all_refs = [] 566for ref in repo.heads: 567all_refs.append((ref, "head")) 568for ref in repo.tags: 569all_refs.append((ref, "tag")) 570 571if os.path.isdir(os.path.join(server_repo_location, subpath)): 572files = [] 573blobs = [] 574 575for entry in os.listdir(os.path.join(server_repo_location, subpath)): 576if not os.path.basename(entry) == ".git": 577files.append(os.path.join(subpath, entry)) 578 579infos = [] 580 581for file in files: 582path = os.path.join(server_repo_location, file) 583mimetype = guess_mime(path) 584 585text = git_command(server_repo_location, None, "log", "--format='%H\n'", 586shlex.quote(file)).decode() 587 588sha = text.split("\n")[0] 589identifier = f"/{username}/{repository}/{sha}" 590 591last_commit = db.session.get(Commit, identifier) 592 593info = { 594"name": os.path.basename(file), 595"serverPath": path, 596"relativePath": file, 597"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file), 598"size": human_size(os.path.getsize(path)), 599"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}", 600"commit": last_commit, 601"shaSize": 7, 602} 603 604special_icon = config.match_icon(os.path.basename(file)) 605if special_icon: 606info["icon"] = special_icon 607elif os.path.isdir(path): 608info["icon"] = config.folder_icon 609elif mimetypes.guess_type(path)[0] in config.file_icons: 610info["icon"] = config.file_icons[mimetypes.guess_type(path)[0]] 611else: 612info["icon"] = config.unknown_icon 613 614if os.path.isdir(path): 615infos.insert(0, info) 616else: 617infos.append(info) 618 619return flask.render_template( 620"repo-tree.html", 621username=username, 622repository=repository, 623files=infos, 624subpath=os.path.join("/", subpath), 625branches=all_refs, 626current=branch, 627remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 628is_favourite=get_favourite(flask.session.get("username"), username, repository) 629) 630else: 631path = os.path.join(server_repo_location, subpath) 632 633if not os.path.exists(path): 634return flask.render_template("not-found.html"), 404 635 636mimetype = guess_mime(path) 637mode = mimetype.split("/", 1)[0] 638size = human_size(os.path.getsize(path)) 639 640special_icon = config.match_icon(os.path.basename(path)) 641if special_icon: 642icon = special_icon 643elif os.path.isdir(path): 644icon = config.folder_icon 645elif mimetypes.guess_type(path)[0] in config.file_icons: 646icon = config.file_icons[mimetypes.guess_type(path)[0]] 647else: 648icon = config.unknown_icon 649 650contents = None 651if mode == "text": 652contents = convert_to_html(path) 653 654return flask.render_template( 655"repo-file.html", 656username=username, 657repository=repository, 658file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath), 659branches=all_refs, 660current=branch, 661mode=mode, 662mimetype=mimetype, 663detailedtype=magic.from_file(path), 664size=size, 665icon=icon, 666subpath=os.path.join("/", subpath), 667extension=pathlib.Path(path).suffix, 668basename=os.path.basename(path), 669contents=contents, 670remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 671is_favourite=get_favourite(flask.session.get("username"), username, repository) 672) 673 674 675@repositories.route("/<username>/<repository>/commit/<sha>") 676def repository_commit(username, repository, sha): 677server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 678if not os.path.exists(server_repo_location): 679app.logger.error(f"Cannot load {server_repo_location}") 680flask.abort(404) 681if not (get_visibility(username, repository) or get_permission_level( 682flask.session.get("username"), username, 683repository) is not None): 684flask.abort(403) 685 686app.logger.info(f"Loading {server_repo_location}") 687 688if not os.path.exists(server_repo_location): 689app.logger.error(f"Cannot load {server_repo_location}") 690return flask.render_template("not-found.html"), 404 691 692repo = git.Repo(server_repo_location) 693repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 694 695files = git_command(os.path.join(server_repo_location, ".git"), None, "diff-tree", "-r", 696"--name-only", "--no-commit-id", sha).decode().split("\n")[:-1] 697 698print(files) 699 700return flask.render_template( 701"repo-commit.html", 702username=username, 703repository=repository, 704remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 705is_favourite=get_favourite(flask.session.get("username"), username, repository), 706diff={file: git_command(os.path.join(server_repo_location, ".git"), None, "diff", 707str(sha) + "^!", "--", file).decode().split("\n") for 708file in files}, 709data=db.session.get(Commit, f"/{username}/{repository}/{sha}"), 710) 711 712 713@repositories.route("/<username>/<repository>/forum/") 714def repository_forum(username, repository): 715server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 716if not os.path.exists(server_repo_location): 717app.logger.error(f"Cannot load {server_repo_location}") 718flask.abort(404) 719if not (get_visibility(username, repository) or get_permission_level( 720flask.session.get("username"), username, 721repository) is not None): 722flask.abort(403) 723 724app.logger.info(f"Loading {server_repo_location}") 725 726if not os.path.exists(server_repo_location): 727app.logger.error(f"Cannot load {server_repo_location}") 728return flask.render_template("not-found.html"), 404 729 730repo = git.Repo(server_repo_location) 731repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 732user = User.query.filter_by(username=flask.session.get("username")).first() 733relationships = RepoAccess.query.filter_by(repo=repo_data) 734user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 735 736return flask.render_template( 737"repo-forum.html", 738username=username, 739repository=repository, 740repo_data=repo_data, 741relationships=relationships, 742repo=repo, 743user_relationship=user_relationship, 744Post=Post, 745remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 746is_favourite=get_favourite(flask.session.get("username"), username, repository), 747default_branch=repo_data.default_branch 748) 749 750 751@repositories.route("/<username>/<repository>/forum/topic/<int:id>") 752def repository_forum_topic(username, repository, id): 753server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 754if not os.path.exists(server_repo_location): 755app.logger.error(f"Cannot load {server_repo_location}") 756flask.abort(404) 757if not (get_visibility(username, repository) or get_permission_level( 758flask.session.get("username"), username, 759repository) is not None): 760flask.abort(403) 761 762app.logger.info(f"Loading {server_repo_location}") 763 764if not os.path.exists(server_repo_location): 765app.logger.error(f"Cannot load {server_repo_location}") 766return flask.render_template("not-found.html"), 404 767 768repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 769user = User.query.filter_by(username=flask.session.get("username")).first() 770relationships = RepoAccess.query.filter_by(repo=repo_data) 771user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 772 773post = Post.query.filter_by(id=id).first() 774 775return flask.render_template( 776"repo-topic.html", 777username=username, 778repository=repository, 779repo_data=repo_data, 780relationships=relationships, 781user_relationship=user_relationship, 782post=post, 783remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 784is_favourite=get_favourite(flask.session.get("username"), username, repository), 785default_branch=repo_data.default_branch 786) 787 788 789@repositories.route("/<username>/<repository>/forum/new", methods=["POST", "GET"]) 790def repository_forum_new(username, repository): 791server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 792if not os.path.exists(server_repo_location): 793app.logger.error(f"Cannot load {server_repo_location}") 794flask.abort(404) 795if not (get_visibility(username, repository) or get_permission_level( 796flask.session.get("username"), username, 797repository) is not None): 798flask.abort(403) 799 800app.logger.info(f"Loading {server_repo_location}") 801 802if not os.path.exists(server_repo_location): 803app.logger.error(f"Cannot load {server_repo_location}") 804return flask.render_template("not-found.html"), 404 805 806repo = git.Repo(server_repo_location) 807repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 808user = User.query.filter_by(username=flask.session.get("username")).first() 809relationships = RepoAccess.query.filter_by(repo=repo_data) 810user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 811 812post = Post(user, repo_data, None, flask.request.form["subject"], 813flask.request.form["message"]) 814 815db.session.add(post) 816db.session.commit() 817 818return flask.redirect( 819flask.url_for(".repository_forum_thread", username=username, repository=repository, 820post_id=post.number), 821code=303) 822 823 824@repositories.route("/<username>/<repository>/forum/<int:post_id>") 825def repository_forum_thread(username, repository, post_id): 826server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 827if not os.path.exists(server_repo_location): 828app.logger.error(f"Cannot load {server_repo_location}") 829flask.abort(404) 830if not (get_visibility(username, repository) or get_permission_level( 831flask.session.get("username"), username, 832repository) is not None): 833flask.abort(403) 834 835app.logger.info(f"Loading {server_repo_location}") 836 837if not os.path.exists(server_repo_location): 838app.logger.error(f"Cannot load {server_repo_location}") 839return flask.render_template("not-found.html"), 404 840 841repo = git.Repo(server_repo_location) 842repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 843user = User.query.filter_by(username=flask.session.get("username")).first() 844relationships = RepoAccess.query.filter_by(repo=repo_data) 845user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 846 847return flask.render_template( 848"repo-forum-thread.html", 849username=username, 850repository=repository, 851repo_data=repo_data, 852relationships=relationships, 853repo=repo, 854Post=Post, 855user_relationship=user_relationship, 856post_id=post_id, 857max_post_nesting=4, 858remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 859is_favourite=get_favourite(flask.session.get("username"), username, repository), 860parent=Post.query.filter_by(repo=repo_data, number=post_id).first(), 861) 862 863 864@repositories.route("/<username>/<repository>/forum/<int:post_id>/change-state", 865methods=["POST"]) 866def repository_forum_change_state(username, repository, post_id): 867server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 868if not os.path.exists(server_repo_location): 869app.logger.error(f"Cannot load {server_repo_location}") 870flask.abort(404) 871if not (get_visibility(username, repository) or get_permission_level( 872flask.session.get("username"), username, 873repository) is not None): 874flask.abort(403) 875 876app.logger.info(f"Loading {server_repo_location}") 877 878repo = git.Repo(server_repo_location) 879repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 880user = User.query.filter_by(username=flask.session.get("username")).first() 881relationships = RepoAccess.query.filter_by(repo=repo_data) 882user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 883 884post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 885 886if not post: 887flask.abort(404) 888 889post.state = int(flask.request.form["new-state"]) 890 891db.session.commit() 892 893return flask.redirect( 894flask.url_for(".repository_forum_thread", username=username, repository=repository, 895post_id=post_id), 896code=303) 897 898 899@repositories.route("/<username>/<repository>/forum/<int:post_id>/reply", methods=["POST"]) 900def repository_forum_reply(username, repository, post_id): 901server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 902if not os.path.exists(server_repo_location): 903app.logger.error(f"Cannot load {server_repo_location}") 904flask.abort(404) 905if not (get_visibility(username, repository) or get_permission_level( 906flask.session.get("username"), username, 907repository) is not None): 908flask.abort(403) 909 910app.logger.info(f"Loading {server_repo_location}") 911 912if not os.path.exists(server_repo_location): 913app.logger.error(f"Cannot load {server_repo_location}") 914return flask.render_template("not-found.html"), 404 915 916repo = git.Repo(server_repo_location) 917repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 918user = User.query.filter_by(username=flask.session.get("username")).first() 919relationships = RepoAccess.query.filter_by(repo=repo_data) 920user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 921if not user: 922flask.abort(401) 923 924parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 925post = Post(user, repo_data, parent, flask.request.form["subject"], 926flask.request.form["message"]) 927 928db.session.add(post) 929post.update_date() 930db.session.commit() 931 932return flask.redirect( 933flask.url_for(".repository_forum_thread", username=username, repository=repository, 934post_id=post_id), 935code=303) 936 937 938@repositories.route("/<username>/<repository>/forum/<int:post_id>/voteup", 939defaults={"score": 1}) 940@repositories.route("/<username>/<repository>/forum/<int:post_id>/votedown", 941defaults={"score": -1}) 942@repositories.route("/<username>/<repository>/forum/<int:post_id>/votes", defaults={"score": 0}) 943def repository_forum_vote(username, repository, post_id, score): 944server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 945if not os.path.exists(server_repo_location): 946app.logger.error(f"Cannot load {server_repo_location}") 947flask.abort(404) 948if not (get_visibility(username, repository) or get_permission_level( 949flask.session.get("username"), username, 950repository) is not None): 951flask.abort(403) 952 953app.logger.info(f"Loading {server_repo_location}") 954 955if not os.path.exists(server_repo_location): 956app.logger.error(f"Cannot load {server_repo_location}") 957return flask.render_template("not-found.html"), 404 958 959repo = git.Repo(server_repo_location) 960repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 961user = User.query.filter_by(username=flask.session.get("username")).first() 962relationships = RepoAccess.query.filter_by(repo=repo_data) 963user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 964if not user: 965flask.abort(401) 966 967post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 968 969if score: 970old_relationship = PostVote.query.filter_by(user_username=user.username, 971post_identifier=post.identifier).first() 972if old_relationship: 973if score == old_relationship.vote_score: 974db.session.delete(old_relationship) 975post.vote_sum -= old_relationship.vote_score 976else: 977post.vote_sum -= old_relationship.vote_score 978post.vote_sum += score 979old_relationship.vote_score = score 980else: 981relationship = PostVote(user, post, score) 982post.vote_sum += score 983db.session.add(relationship) 984 985db.session.commit() 986 987user_vote = PostVote.query.filter_by(user_username=user.username, 988post_identifier=post.identifier).first() 989response = flask.make_response( 990str(post.vote_sum) + " " + str(user_vote.vote_score if user_vote else 0)) 991response.content_type = "text/plain" 992 993return response 994 995 996@repositories.route("/<username>/<repository>/favourite") 997def repository_favourite(username, repository): 998server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 999if not os.path.exists(server_repo_location): 1000app.logger.error(f"Cannot load {server_repo_location}") 1001flask.abort(404) 1002if not (get_visibility(username, repository) or get_permission_level( 1003flask.session.get("username"), username, 1004repository) is not None): 1005flask.abort(403) 1006 1007app.logger.info(f"Loading {server_repo_location}") 1008 1009if not os.path.exists(server_repo_location): 1010app.logger.error(f"Cannot load {server_repo_location}") 1011return flask.render_template("not-found.html"), 404 1012 1013repo = git.Repo(server_repo_location) 1014repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1015user = User.query.filter_by(username=flask.session.get("username")).first() 1016relationships = RepoAccess.query.filter_by(repo=repo_data) 1017user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 1018if not user: 1019flask.abort(401) 1020 1021old_relationship = RepoFavourite.query.filter_by(user_username=user.username, 1022repo_route=repo_data.route).first() 1023if old_relationship: 1024db.session.delete(old_relationship) 1025else: 1026relationship = RepoFavourite(user, repo_data) 1027db.session.add(relationship) 1028 1029db.session.commit() 1030 1031return flask.redirect(flask.url_for("favourites"), code=303) 1032 1033 1034@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"]) 1035def repository_users(username, repository): 1036server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1037if not os.path.exists(server_repo_location): 1038app.logger.error(f"Cannot load {server_repo_location}") 1039flask.abort(404) 1040if not (get_visibility(username, repository) or get_permission_level( 1041flask.session.get("username"), username, 1042repository) is not None): 1043flask.abort(403) 1044 1045app.logger.info(f"Loading {server_repo_location}") 1046 1047if not os.path.exists(server_repo_location): 1048app.logger.error(f"Cannot load {server_repo_location}") 1049return flask.render_template("not-found.html"), 404 1050 1051repo = git.Repo(server_repo_location) 1052repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1053user = User.query.filter_by(username=flask.session.get("username")).first() 1054relationships = RepoAccess.query.filter_by(repo=repo_data) 1055user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 1056 1057if flask.request.method == "GET": 1058return flask.render_template( 1059"repo-users.html", 1060username=username, 1061repository=repository, 1062repo_data=repo_data, 1063relationships=relationships, 1064repo=repo, 1065user_relationship=user_relationship, 1066remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1067is_favourite=get_favourite(flask.session.get("username"), username, repository) 1068) 1069else: 1070if get_permission_level(flask.session.get("username"), username, repository) != 2: 1071flask.abort(401) 1072 1073if flask.request.form.get("new-username"): 1074# Create new relationship 1075new_user = User.query.filter_by( 1076username=flask.request.form.get("new-username")).first() 1077relationship = RepoAccess(new_user, repo_data, flask.request.form.get("new-level")) 1078db.session.add(relationship) 1079db.session.commit() 1080if flask.request.form.get("update-username"): 1081# Create new relationship 1082updated_user = User.query.filter_by( 1083username=flask.request.form.get("update-username")).first() 1084relationship = RepoAccess.query.filter_by(repo=repo_data, user=updated_user).first() 1085if flask.request.form.get("update-level") == -1: 1086relationship.delete() 1087else: 1088relationship.access_level = flask.request.form.get("update-level") 1089db.session.commit() 1090 1091return flask.redirect( 1092app.url_for(".repository_users", username=username, repository=repository)) 1093 1094 1095@repositories.route("/<username>/<repository>/branches/") 1096def repository_branches(username, repository): 1097server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1098if not os.path.exists(server_repo_location): 1099app.logger.error(f"Cannot load {server_repo_location}") 1100flask.abort(404) 1101if not (get_visibility(username, repository) or get_permission_level( 1102flask.session.get("username"), username, 1103repository) is not None): 1104flask.abort(403) 1105 1106app.logger.info(f"Loading {server_repo_location}") 1107 1108if not os.path.exists(server_repo_location): 1109app.logger.error(f"Cannot load {server_repo_location}") 1110return flask.render_template("not-found.html"), 404 1111 1112repo = git.Repo(server_repo_location) 1113repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1114 1115return flask.render_template( 1116"repo-branches.html", 1117username=username, 1118repository=repository, 1119repo_data=repo_data, 1120repo=repo, 1121remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1122is_favourite=get_favourite(flask.session.get("username"), username, repository) 1123) 1124 1125 1126@repositories.route("/<username>/<repository>/log/", defaults={"branch": None}) 1127@repositories.route("/<username>/<repository>/log/<branch>/") 1128def repository_log(username, repository, branch): 1129server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1130if not os.path.exists(server_repo_location): 1131app.logger.error(f"Cannot load {server_repo_location}") 1132flask.abort(404) 1133if not (get_visibility(username, repository) or get_permission_level( 1134flask.session.get("username"), username, 1135repository) is not None): 1136flask.abort(403) 1137 1138app.logger.info(f"Loading {server_repo_location}") 1139 1140if not os.path.exists(server_repo_location): 1141app.logger.error(f"Cannot load {server_repo_location}") 1142return flask.render_template("not-found.html"), 404 1143 1144repo = git.Repo(server_repo_location) 1145repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1146if not repo_data.default_branch: 1147if repo.heads: 1148repo_data.default_branch = repo.heads[0].name 1149else: 1150return flask.render_template("empty.html", 1151remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 1152if not branch: 1153branch = repo_data.default_branch 1154return flask.redirect(f"./{branch}", code=302) 1155 1156if branch.startswith("tag:"): 1157ref = f"tags/{branch[4:]}" 1158elif branch.startswith("~"): 1159ref = branch[1:] 1160else: 1161ref = f"heads/{branch}" 1162 1163ref = ref.replace("~", "/") # encode slashes for URL support 1164 1165try: 1166repo.git.checkout("-f", ref) 1167except git.exc.GitCommandError: 1168return flask.render_template("not-found.html"), 404 1169 1170branches = repo.heads 1171 1172all_refs = [] 1173for ref in repo.heads: 1174all_refs.append((ref, "head")) 1175for ref in repo.tags: 1176all_refs.append((ref, "tag")) 1177 1178commit_list = [f"/{username}/{repository}/{sha}" for sha in 1179git_command(server_repo_location, None, "log", 1180"--format='%H'").decode().split("\n")] 1181 1182commits = Commit.query.filter(Commit.identifier.in_(commit_list)) 1183 1184return flask.render_template( 1185"repo-log.html", 1186username=username, 1187repository=repository, 1188branches=all_refs, 1189current=branch, 1190repo_data=repo_data, 1191repo=repo, 1192commits=commits, 1193remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1194is_favourite=get_favourite(flask.session.get("username"), username, repository) 1195) 1196 1197 1198@repositories.route("/<username>/<repository>/prs/", methods=["GET", "POST"]) 1199def repository_prs(username, repository): 1200server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1201if not os.path.exists(server_repo_location): 1202app.logger.error(f"Cannot load {server_repo_location}") 1203flask.abort(404) 1204if not (get_visibility(username, repository) or get_permission_level( 1205flask.session.get("username"), username, 1206repository) is not None): 1207flask.abort(403) 1208 1209app.logger.info(f"Loading {server_repo_location}") 1210 1211if not os.path.exists(server_repo_location): 1212app.logger.error(f"Cannot load {server_repo_location}") 1213return flask.render_template("not-found.html"), 404 1214 1215if flask.request.method == "GET": 1216repo = git.Repo(server_repo_location) 1217repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1218user = User.query.filter_by(username=flask.session.get("username")).first() 1219 1220return flask.render_template( 1221"repo-prs.html", 1222username=username, 1223repository=repository, 1224repo_data=repo_data, 1225repo=repo, 1226PullRequest=PullRequest, 1227remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1228is_favourite=get_favourite(flask.session.get("username"), username, repository), 1229default_branch=repo_data.default_branch, 1230branches=repo.branches 1231) 1232 1233else: 1234repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1235head = flask.request.form.get("head") 1236head_route = flask.request.form.get("headroute") 1237base = flask.request.form.get("base") 1238 1239if not head and base and head_route: 1240return flask.redirect(".", 400) 1241 1242head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/"))) 1243base_repo = git.Repo(server_repo_location) 1244print(head_repo) 1245 1246if head not in head_repo.branches or base not in base_repo.branches: 1247flask.flash(Markup( 1248"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Bad branch name")), 1249category="error") 1250return flask.redirect(".", 303) 1251 1252head_data = db.session.get(Repo, head_route) 1253if not head_data.visibility: 1254flask.flash(Markup( 1255"<iconify-icon icon='mdi:error'></iconify-icon>" + _( 1256"Head can't be restricted")), 1257category="error") 1258return flask.redirect(".", 303) 1259 1260pull_request = PullRequest(repo_data, head, head_data, base, 1261db.session.get(User, flask.session["username"])) 1262 1263db.session.add(pull_request) 1264db.session.commit() 1265 1266return flask.redirect(".", 303) 1267 1268 1269@repositories.route("/<username>/<repository>/prs/merge", methods=["POST"]) 1270def repository_prs_merge(username, repository): 1271server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1272if not os.path.exists(server_repo_location): 1273app.logger.error(f"Cannot load {server_repo_location}") 1274flask.abort(404) 1275if not (get_visibility(username, repository) or get_permission_level( 1276flask.session.get("username"), username, 1277repository) is not None): 1278flask.abort(403) 1279 1280if not get_permission_level(flask.session.get("username"), username, repository): 1281flask.abort(401) 1282 1283repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1284repo = git.Repo(server_repo_location) 1285id = flask.request.form.get("id") 1286 1287pull_request = db.session.get(PullRequest, id) 1288 1289if pull_request: 1290result = celery_tasks.merge_heads.delay( 1291pull_request.head_route, 1292pull_request.head_branch, 1293pull_request.base_route, 1294pull_request.base_branch, 1295simulate=True 1296) 1297task_result = worker.AsyncResult(result.id) 1298 1299return flask.redirect(f"/task/{result.id}?pr-id={id}", 303) 1300# db.session.delete(pull_request) 1301# db.session.commit() 1302else: 1303flask.abort(400) 1304 1305 1306@repositories.route("/<username>/<repository>/prs/<int:id>/merge") 1307def repository_prs_merge_stage_two(username, repository, id): 1308server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1309if not os.path.exists(server_repo_location): 1310app.logger.error(f"Cannot load {server_repo_location}") 1311flask.abort(404) 1312if not (get_visibility(username, repository) or get_permission_level( 1313flask.session.get("username"), username, 1314repository) is not None): 1315flask.abort(403) 1316 1317if not get_permission_level(flask.session.get("username"), username, repository): 1318flask.abort(401) 1319 1320repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1321repo = git.Repo(server_repo_location) 1322 1323pull_request = db.session.get(PullRequest, id) 1324 1325if pull_request: 1326result = celery_tasks.merge_heads.delay( 1327pull_request.head_route, 1328pull_request.head_branch, 1329pull_request.base_route, 1330pull_request.base_branch, 1331simulate=False 1332) 1333task_result = worker.AsyncResult(result.id) 1334 1335pull_request.state = 1 1336db.session.commit() 1337 1338return flask.redirect(f"/task/{result.id}?pr-id={id}", 303) 1339# db.session.delete(pull_request) 1340else: 1341flask.abort(400) 1342 1343 1344@app.route("/task/<task_id>") 1345def task_monitor(task_id): 1346task_result = worker.AsyncResult(task_id) 1347print(task_result.status) 1348 1349return flask.render_template("task-monitor.html", result=task_result) 1350 1351 1352@repositories.route("/<username>/<repository>/prs/delete", methods=["POST"]) 1353def repository_prs_delete(username, repository): 1354server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1355if not os.path.exists(server_repo_location): 1356app.logger.error(f"Cannot load {server_repo_location}") 1357flask.abort(404) 1358if not (get_visibility(username, repository) or get_permission_level( 1359flask.session.get("username"), username, 1360repository) is not None): 1361flask.abort(403) 1362 1363if not get_permission_level(flask.session.get("username"), username, repository): 1364flask.abort(401) 1365 1366repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1367repo = git.Repo(server_repo_location) 1368id = flask.request.form.get("id") 1369 1370pull_request = db.session.get(PullRequest, id) 1371 1372if pull_request: 1373pull_request.state = 2 1374db.session.commit() 1375 1376return flask.redirect(".", 303) 1377 1378 1379@repositories.route("/<username>/<repository>/settings/") 1380def repository_settings(username, repository): 1381if get_permission_level(flask.session.get("username"), username, repository) != 2: 1382flask.abort(401) 1383 1384return flask.render_template("repo-settings.html", username=username, repository=repository) 1385 1386 1387@app.errorhandler(404) 1388def e404(error): 1389return flask.render_template("not-found.html"), 404 1390 1391 1392@app.errorhandler(401) 1393def e401(error): 1394return flask.render_template("unauthorised.html"), 401 1395 1396 1397@app.errorhandler(403) 1398def e403(error): 1399return flask.render_template("forbidden.html"), 403 1400 1401 1402@app.errorhandler(418) 1403def e418(error): 1404return flask.render_template("teapot.html"), 418 1405 1406 1407@app.errorhandler(405) 1408def e405(error): 1409return flask.render_template("method-not-allowed.html"), 405 1410 1411 1412if __name__ == "__main__": 1413app.run(debug=True, port=8080, host="0.0.0.0") 1414 1415app.register_blueprint(repositories) 1416