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