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