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