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