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): 463server_userdata_location = os.path.join(config.USERDATA_PATH, username) 464if not os.path.exists(server_userdata_location): 465return flask.render_template("not-found.html"), 404 466 467return flask.send_from_directory(server_userdata_location, "avatar.png") 468 469 470@app.route("/info/<username>/avatar", methods=["POST"]) 471def user_avatar_upload(username): 472server_userdata_location = os.path.join(config.USERDATA_PATH, username) 473 474if not os.path.exists(server_userdata_location): 475flask.abort(404) 476if not flask.session.get("username") == username: 477flask.abort(403) 478 479# Convert image to PNG 480try: 481image = Image.open(flask.request.files["avatar"]) 482except: 483flask.abort(400) 484image.save(os.path.join(server_userdata_location, "avatar.png")) 485 486return flask.redirect(f"/{username}", code=303) 487 488 489@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>") 490def repository_raw(username, repository, branch, subpath): 491server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 492if not os.path.exists(server_repo_location): 493app.logger.error(f"Cannot load {server_repo_location}") 494flask.abort(404) 495if not (get_visibility(username, repository) or get_permission_level( 496flask.session.get("username"), username, 497repository) is not None): 498flask.abort(403) 499 500app.logger.info(f"Loading {server_repo_location}") 501 502if not os.path.exists(server_repo_location): 503app.logger.error(f"Cannot load {server_repo_location}") 504return flask.render_template("not-found.html"), 404 505 506repo = git.Repo(server_repo_location) 507repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 508if not repo_data.default_branch: 509if repo.heads: 510repo_data.default_branch = repo.heads[0].name 511else: 512return flask.render_template("empty.html", 513remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 514if not branch: 515branch = repo_data.default_branch 516return flask.redirect(f"./{branch}", code=302) 517 518if branch.startswith("tag:"): 519ref = f"tags/{branch[4:]}" 520elif branch.startswith("~"): 521ref = branch[1:] 522else: 523ref = f"heads/{branch}" 524 525ref = ref.replace("~", "/") # encode slashes for URL support 526 527try: 528repo.git.checkout("-f", ref) 529except git.exc.GitCommandError: 530return flask.render_template("not-found.html"), 404 531 532return flask.send_from_directory(config.REPOS_PATH, 533os.path.join(username, repository, subpath)) 534 535 536@repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""}) 537@repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""}) 538@repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>") 539def repository_tree(username, repository, branch, subpath): 540server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 541if not os.path.exists(server_repo_location): 542app.logger.error(f"Cannot load {server_repo_location}") 543flask.abort(404) 544if not (get_visibility(username, repository) or get_permission_level( 545flask.session.get("username"), username, 546repository) is not None): 547flask.abort(403) 548 549app.logger.info(f"Loading {server_repo_location}") 550 551repo = git.Repo(server_repo_location) 552repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 553if not repo_data.default_branch: 554if repo.heads: 555repo_data.default_branch = repo.heads[0].name 556else: 557return flask.render_template("empty.html", 558remote=f"{config.www_protocol}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 559if not branch: 560branch = repo_data.default_branch 561return flask.redirect(f"./{branch}", code=302) 562 563if branch.startswith("tag:"): 564ref = f"tags/{branch[4:]}" 565elif branch.startswith("~"): 566ref = branch[1:] 567else: 568ref = f"heads/{branch}" 569 570ref = ref.replace("~", "/") # encode slashes for URL support 571 572try: 573repo.git.checkout("-f", ref) 574except git.exc.GitCommandError: 575return flask.render_template("not-found.html"), 404 576 577branches = repo.heads 578 579all_refs = [] 580for ref in repo.heads: 581all_refs.append((ref, "head")) 582for ref in repo.tags: 583all_refs.append((ref, "tag")) 584 585if os.path.isdir(os.path.join(server_repo_location, subpath)): 586files = [] 587blobs = [] 588 589for entry in os.listdir(os.path.join(server_repo_location, subpath)): 590if not os.path.basename(entry) == ".git": 591files.append(os.path.join(subpath, entry)) 592 593infos = [] 594 595for file in files: 596path = os.path.join(server_repo_location, file) 597mimetype = guess_mime(path) 598 599text = git_command(server_repo_location, None, "log", "--format='%H\n'", 600shlex.quote(file)).decode() 601 602sha = text.split("\n")[0] 603identifier = f"/{username}/{repository}/{sha}" 604 605last_commit = db.session.get(Commit, identifier) 606 607info = { 608"name": os.path.basename(file), 609"serverPath": path, 610"relativePath": file, 611"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file), 612"size": human_size(os.path.getsize(path)), 613"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}", 614"commit": last_commit, 615"shaSize": 7, 616} 617 618special_icon = config.match_icon(os.path.basename(file)) 619if special_icon: 620info["icon"] = special_icon 621elif os.path.isdir(path): 622info["icon"] = config.folder_icon 623elif mimetypes.guess_type(path)[0] in config.file_icons: 624info["icon"] = config.file_icons[mimetypes.guess_type(path)[0]] 625else: 626info["icon"] = config.unknown_icon 627 628if os.path.isdir(path): 629infos.insert(0, info) 630else: 631infos.append(info) 632 633return flask.render_template( 634"repo-tree.html", 635username=username, 636repository=repository, 637files=infos, 638subpath=os.path.join("/", subpath), 639branches=all_refs, 640current=branch, 641remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 642is_favourite=get_favourite(flask.session.get("username"), username, repository) 643) 644else: 645path = os.path.join(server_repo_location, subpath) 646 647if not os.path.exists(path): 648return flask.render_template("not-found.html"), 404 649 650mimetype = guess_mime(path) 651mode = mimetype.split("/", 1)[0] 652size = human_size(os.path.getsize(path)) 653 654special_icon = config.match_icon(os.path.basename(path)) 655if special_icon: 656icon = special_icon 657elif os.path.isdir(path): 658icon = config.folder_icon 659elif mimetypes.guess_type(path)[0] in config.file_icons: 660icon = config.file_icons[mimetypes.guess_type(path)[0]] 661else: 662icon = config.unknown_icon 663 664contents = None 665if mode == "text": 666contents = convert_to_html(path) 667 668return flask.render_template( 669"repo-file.html", 670username=username, 671repository=repository, 672file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath), 673branches=all_refs, 674current=branch, 675mode=mode, 676mimetype=mimetype, 677detailedtype=magic.from_file(path), 678size=size, 679icon=icon, 680subpath=os.path.join("/", subpath), 681extension=pathlib.Path(path).suffix, 682basename=os.path.basename(path), 683contents=contents, 684remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 685is_favourite=get_favourite(flask.session.get("username"), username, repository) 686) 687 688 689@repositories.route("/<username>/<repository>/commit/<sha>") 690def repository_commit(username, repository, sha): 691server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 692if not os.path.exists(server_repo_location): 693app.logger.error(f"Cannot load {server_repo_location}") 694flask.abort(404) 695if not (get_visibility(username, repository) or get_permission_level( 696flask.session.get("username"), username, 697repository) is not None): 698flask.abort(403) 699 700app.logger.info(f"Loading {server_repo_location}") 701 702if not os.path.exists(server_repo_location): 703app.logger.error(f"Cannot load {server_repo_location}") 704return flask.render_template("not-found.html"), 404 705 706repo = git.Repo(server_repo_location) 707repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 708 709files = git_command(os.path.join(server_repo_location, ".git"), None, "diff-tree", "-r", 710"--name-only", "--no-commit-id", sha).decode().split("\n")[:-1] 711 712print(files) 713 714return flask.render_template( 715"repo-commit.html", 716username=username, 717repository=repository, 718remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 719is_favourite=get_favourite(flask.session.get("username"), username, repository), 720diff={file: git_command(os.path.join(server_repo_location, ".git"), None, "diff", 721str(sha) + "^!", "--", file).decode().split("\n") for 722file in files}, 723data=db.session.get(Commit, f"/{username}/{repository}/{sha}"), 724) 725 726 727@repositories.route("/<username>/<repository>/forum/") 728def repository_forum(username, repository): 729server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 730if not os.path.exists(server_repo_location): 731app.logger.error(f"Cannot load {server_repo_location}") 732flask.abort(404) 733if not (get_visibility(username, repository) or get_permission_level( 734flask.session.get("username"), username, 735repository) is not None): 736flask.abort(403) 737 738app.logger.info(f"Loading {server_repo_location}") 739 740if not os.path.exists(server_repo_location): 741app.logger.error(f"Cannot load {server_repo_location}") 742return flask.render_template("not-found.html"), 404 743 744repo = git.Repo(server_repo_location) 745repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 746user = User.query.filter_by(username=flask.session.get("username")).first() 747relationships = RepoAccess.query.filter_by(repo=repo_data) 748user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 749 750return flask.render_template( 751"repo-forum.html", 752username=username, 753repository=repository, 754repo_data=repo_data, 755relationships=relationships, 756repo=repo, 757user_relationship=user_relationship, 758Post=Post, 759remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 760is_favourite=get_favourite(flask.session.get("username"), username, repository), 761default_branch=repo_data.default_branch 762) 763 764 765@repositories.route("/<username>/<repository>/forum/topic/<int:id>") 766def repository_forum_topic(username, repository, id): 767server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 768if not os.path.exists(server_repo_location): 769app.logger.error(f"Cannot load {server_repo_location}") 770flask.abort(404) 771if not (get_visibility(username, repository) or get_permission_level( 772flask.session.get("username"), username, 773repository) is not None): 774flask.abort(403) 775 776app.logger.info(f"Loading {server_repo_location}") 777 778if not os.path.exists(server_repo_location): 779app.logger.error(f"Cannot load {server_repo_location}") 780return flask.render_template("not-found.html"), 404 781 782repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 783user = User.query.filter_by(username=flask.session.get("username")).first() 784relationships = RepoAccess.query.filter_by(repo=repo_data) 785user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 786 787post = Post.query.filter_by(id=id).first() 788 789return flask.render_template( 790"repo-topic.html", 791username=username, 792repository=repository, 793repo_data=repo_data, 794relationships=relationships, 795user_relationship=user_relationship, 796post=post, 797remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 798is_favourite=get_favourite(flask.session.get("username"), username, repository), 799default_branch=repo_data.default_branch 800) 801 802 803@repositories.route("/<username>/<repository>/forum/new", methods=["POST", "GET"]) 804def repository_forum_new(username, repository): 805server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 806if not os.path.exists(server_repo_location): 807app.logger.error(f"Cannot load {server_repo_location}") 808flask.abort(404) 809if not (get_visibility(username, repository) or get_permission_level( 810flask.session.get("username"), username, 811repository) is not None): 812flask.abort(403) 813 814app.logger.info(f"Loading {server_repo_location}") 815 816if not os.path.exists(server_repo_location): 817app.logger.error(f"Cannot load {server_repo_location}") 818return flask.render_template("not-found.html"), 404 819 820repo = git.Repo(server_repo_location) 821repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 822user = User.query.filter_by(username=flask.session.get("username")).first() 823relationships = RepoAccess.query.filter_by(repo=repo_data) 824user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 825 826post = Post(user, repo_data, None, flask.request.form["subject"], 827flask.request.form["message"]) 828 829db.session.add(post) 830db.session.commit() 831 832return flask.redirect( 833flask.url_for(".repository_forum_thread", username=username, repository=repository, 834post_id=post.number), 835code=303) 836 837 838@repositories.route("/<username>/<repository>/forum/<int:post_id>") 839def repository_forum_thread(username, repository, post_id): 840server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 841if not os.path.exists(server_repo_location): 842app.logger.error(f"Cannot load {server_repo_location}") 843flask.abort(404) 844if not (get_visibility(username, repository) or get_permission_level( 845flask.session.get("username"), username, 846repository) is not None): 847flask.abort(403) 848 849app.logger.info(f"Loading {server_repo_location}") 850 851if not os.path.exists(server_repo_location): 852app.logger.error(f"Cannot load {server_repo_location}") 853return flask.render_template("not-found.html"), 404 854 855repo = git.Repo(server_repo_location) 856repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 857user = User.query.filter_by(username=flask.session.get("username")).first() 858relationships = RepoAccess.query.filter_by(repo=repo_data) 859user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 860 861return flask.render_template( 862"repo-forum-thread.html", 863username=username, 864repository=repository, 865repo_data=repo_data, 866relationships=relationships, 867repo=repo, 868Post=Post, 869user_relationship=user_relationship, 870post_id=post_id, 871max_post_nesting=4, 872remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 873is_favourite=get_favourite(flask.session.get("username"), username, repository), 874parent=Post.query.filter_by(repo=repo_data, number=post_id).first(), 875has_permission=not ((not get_permission_level(flask.session.get("username"), username, 876repository)) and db.session.get(Post, 877f"/{username}/{repository}/{post_id}").owner.username != flask.session.get("username")), 878) 879 880 881@repositories.route("/<username>/<repository>/forum/<int:post_id>/change-state", 882methods=["POST"]) 883def repository_forum_change_state(username, repository, post_id): 884server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 885if not os.path.exists(server_repo_location): 886app.logger.error(f"Cannot load {server_repo_location}") 887flask.abort(404) 888if (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"): 889flask.abort(403) 890 891app.logger.info(f"Loading {server_repo_location}") 892 893repo = git.Repo(server_repo_location) 894repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 895user = User.query.filter_by(username=flask.session.get("username")).first() 896relationships = RepoAccess.query.filter_by(repo=repo_data) 897user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 898 899post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 900 901if not post: 902flask.abort(404) 903 904post.state = int(flask.request.form["new-state"]) 905 906db.session.commit() 907 908return flask.redirect( 909flask.url_for(".repository_forum_thread", username=username, repository=repository, 910post_id=post_id), 911code=303) 912 913 914@repositories.route("/<username>/<repository>/forum/<int:post_id>/reply", methods=["POST"]) 915def repository_forum_reply(username, repository, post_id): 916server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 917if not os.path.exists(server_repo_location): 918app.logger.error(f"Cannot load {server_repo_location}") 919flask.abort(404) 920if not (get_visibility(username, repository) or get_permission_level( 921flask.session.get("username"), username, 922repository) is not None): 923flask.abort(403) 924 925app.logger.info(f"Loading {server_repo_location}") 926 927if not os.path.exists(server_repo_location): 928app.logger.error(f"Cannot load {server_repo_location}") 929return flask.render_template("not-found.html"), 404 930 931repo = git.Repo(server_repo_location) 932repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 933user = User.query.filter_by(username=flask.session.get("username")).first() 934relationships = RepoAccess.query.filter_by(repo=repo_data) 935user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 936if not user: 937flask.abort(401) 938 939parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 940post = Post(user, repo_data, parent, flask.request.form["subject"], 941flask.request.form["message"]) 942 943db.session.add(post) 944post.update_date() 945db.session.commit() 946 947return flask.redirect( 948flask.url_for(".repository_forum_thread", username=username, repository=repository, 949post_id=post_id), 950code=303) 951 952 953@repositories.route("/<username>/<repository>/forum/<int:post_id>/voteup", 954defaults={"score": 1}) 955@repositories.route("/<username>/<repository>/forum/<int:post_id>/votedown", 956defaults={"score": -1}) 957@repositories.route("/<username>/<repository>/forum/<int:post_id>/votes", defaults={"score": 0}) 958def repository_forum_vote(username, repository, post_id, score): 959server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 960if not os.path.exists(server_repo_location): 961app.logger.error(f"Cannot load {server_repo_location}") 962flask.abort(404) 963if not (get_visibility(username, repository) or get_permission_level( 964flask.session.get("username"), username, 965repository) is not None): 966flask.abort(403) 967 968app.logger.info(f"Loading {server_repo_location}") 969 970if not os.path.exists(server_repo_location): 971app.logger.error(f"Cannot load {server_repo_location}") 972return flask.render_template("not-found.html"), 404 973 974repo = git.Repo(server_repo_location) 975repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 976user = User.query.filter_by(username=flask.session.get("username")).first() 977relationships = RepoAccess.query.filter_by(repo=repo_data) 978user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 979if not user: 980flask.abort(401) 981 982post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 983 984if score: 985old_relationship = PostVote.query.filter_by(user_username=user.username, 986post_identifier=post.identifier).first() 987if old_relationship: 988if score == old_relationship.vote_score: 989db.session.delete(old_relationship) 990post.vote_sum -= old_relationship.vote_score 991else: 992post.vote_sum -= old_relationship.vote_score 993post.vote_sum += score 994old_relationship.vote_score = score 995else: 996relationship = PostVote(user, post, score) 997post.vote_sum += score 998db.session.add(relationship) 999 1000db.session.commit() 1001 1002user_vote = PostVote.query.filter_by(user_username=user.username, 1003post_identifier=post.identifier).first() 1004response = flask.make_response( 1005str(post.vote_sum) + " " + str(user_vote.vote_score if user_vote else 0)) 1006response.content_type = "text/plain" 1007 1008return response 1009 1010 1011@repositories.route("/<username>/<repository>/favourite") 1012def repository_favourite(username, repository): 1013server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1014if not os.path.exists(server_repo_location): 1015app.logger.error(f"Cannot load {server_repo_location}") 1016flask.abort(404) 1017if not (get_visibility(username, repository) or get_permission_level( 1018flask.session.get("username"), username, 1019repository) is not None): 1020flask.abort(403) 1021 1022app.logger.info(f"Loading {server_repo_location}") 1023 1024if not os.path.exists(server_repo_location): 1025app.logger.error(f"Cannot load {server_repo_location}") 1026return flask.render_template("not-found.html"), 404 1027 1028repo = git.Repo(server_repo_location) 1029repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1030user = User.query.filter_by(username=flask.session.get("username")).first() 1031relationships = RepoAccess.query.filter_by(repo=repo_data) 1032user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 1033if not user: 1034flask.abort(401) 1035 1036old_relationship = RepoFavourite.query.filter_by(user_username=user.username, 1037repo_route=repo_data.route).first() 1038if old_relationship: 1039db.session.delete(old_relationship) 1040else: 1041relationship = RepoFavourite(user, repo_data) 1042db.session.add(relationship) 1043 1044db.session.commit() 1045 1046return flask.redirect(flask.url_for("favourites"), code=303) 1047 1048 1049@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"]) 1050def repository_users(username, repository): 1051server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1052if not os.path.exists(server_repo_location): 1053app.logger.error(f"Cannot load {server_repo_location}") 1054flask.abort(404) 1055if not (get_visibility(username, repository) or get_permission_level( 1056flask.session.get("username"), username, 1057repository) is not None): 1058flask.abort(403) 1059 1060app.logger.info(f"Loading {server_repo_location}") 1061 1062if not os.path.exists(server_repo_location): 1063app.logger.error(f"Cannot load {server_repo_location}") 1064return flask.render_template("not-found.html"), 404 1065 1066repo = git.Repo(server_repo_location) 1067repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1068user = User.query.filter_by(username=flask.session.get("username")).first() 1069relationships = RepoAccess.query.filter_by(repo=repo_data) 1070user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 1071 1072if flask.request.method == "GET": 1073return flask.render_template( 1074"repo-users.html", 1075username=username, 1076repository=repository, 1077repo_data=repo_data, 1078relationships=relationships, 1079repo=repo, 1080user_relationship=user_relationship, 1081remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1082is_favourite=get_favourite(flask.session.get("username"), username, repository) 1083) 1084else: 1085if get_permission_level(flask.session.get("username"), username, repository) != 2: 1086flask.abort(401) 1087 1088if flask.request.form.get("new-username"): 1089# Create new relationship 1090new_user = User.query.filter_by( 1091username=flask.request.form.get("new-username")).first() 1092relationship = RepoAccess(new_user, repo_data, flask.request.form.get("new-level")) 1093db.session.add(relationship) 1094db.session.commit() 1095if flask.request.form.get("update-username"): 1096# Create new relationship 1097updated_user = User.query.filter_by( 1098username=flask.request.form.get("update-username")).first() 1099relationship = RepoAccess.query.filter_by(repo=repo_data, user=updated_user).first() 1100if flask.request.form.get("update-level") == -1: 1101relationship.delete() 1102else: 1103relationship.access_level = flask.request.form.get("update-level") 1104db.session.commit() 1105 1106return flask.redirect( 1107app.url_for(".repository_users", username=username, repository=repository)) 1108 1109 1110@repositories.route("/<username>/<repository>/branches/") 1111def repository_branches(username, repository): 1112server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1113if not os.path.exists(server_repo_location): 1114app.logger.error(f"Cannot load {server_repo_location}") 1115flask.abort(404) 1116if not (get_visibility(username, repository) or get_permission_level( 1117flask.session.get("username"), username, 1118repository) is not None): 1119flask.abort(403) 1120 1121app.logger.info(f"Loading {server_repo_location}") 1122 1123if not os.path.exists(server_repo_location): 1124app.logger.error(f"Cannot load {server_repo_location}") 1125return flask.render_template("not-found.html"), 404 1126 1127repo = git.Repo(server_repo_location) 1128repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1129 1130return flask.render_template( 1131"repo-branches.html", 1132username=username, 1133repository=repository, 1134repo_data=repo_data, 1135repo=repo, 1136remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1137is_favourite=get_favourite(flask.session.get("username"), username, repository) 1138) 1139 1140 1141@repositories.route("/<username>/<repository>/log/", defaults={"branch": None}) 1142@repositories.route("/<username>/<repository>/log/<branch>/") 1143def repository_log(username, repository, branch): 1144server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1145if not os.path.exists(server_repo_location): 1146app.logger.error(f"Cannot load {server_repo_location}") 1147flask.abort(404) 1148if not (get_visibility(username, repository) or get_permission_level( 1149flask.session.get("username"), username, 1150repository) is not None): 1151flask.abort(403) 1152 1153app.logger.info(f"Loading {server_repo_location}") 1154 1155if not os.path.exists(server_repo_location): 1156app.logger.error(f"Cannot load {server_repo_location}") 1157return flask.render_template("not-found.html"), 404 1158 1159repo = git.Repo(server_repo_location) 1160repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1161if not repo_data.default_branch: 1162if repo.heads: 1163repo_data.default_branch = repo.heads[0].name 1164else: 1165return flask.render_template("empty.html", 1166remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 1167if not branch: 1168branch = repo_data.default_branch 1169return flask.redirect(f"./{branch}", code=302) 1170 1171if branch.startswith("tag:"): 1172ref = f"tags/{branch[4:]}" 1173elif branch.startswith("~"): 1174ref = branch[1:] 1175else: 1176ref = f"heads/{branch}" 1177 1178ref = ref.replace("~", "/") # encode slashes for URL support 1179 1180try: 1181repo.git.checkout("-f", ref) 1182except git.exc.GitCommandError: 1183return flask.render_template("not-found.html"), 404 1184 1185branches = repo.heads 1186 1187all_refs = [] 1188for ref in repo.heads: 1189all_refs.append((ref, "head")) 1190for ref in repo.tags: 1191all_refs.append((ref, "tag")) 1192 1193commit_list = [f"/{username}/{repository}/{sha}" for sha in 1194git_command(server_repo_location, None, "log", 1195"--format='%H'").decode().split("\n")] 1196 1197commits = Commit.query.filter(Commit.identifier.in_(commit_list)) 1198 1199return flask.render_template( 1200"repo-log.html", 1201username=username, 1202repository=repository, 1203branches=all_refs, 1204current=branch, 1205repo_data=repo_data, 1206repo=repo, 1207commits=commits, 1208remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1209is_favourite=get_favourite(flask.session.get("username"), username, repository) 1210) 1211 1212 1213@repositories.route("/<username>/<repository>/prs/", methods=["GET", "POST"]) 1214def repository_prs(username, repository): 1215server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1216if not os.path.exists(server_repo_location): 1217app.logger.error(f"Cannot load {server_repo_location}") 1218flask.abort(404) 1219if not (get_visibility(username, repository) or get_permission_level( 1220flask.session.get("username"), username, 1221repository) is not None): 1222flask.abort(403) 1223 1224app.logger.info(f"Loading {server_repo_location}") 1225 1226if not os.path.exists(server_repo_location): 1227app.logger.error(f"Cannot load {server_repo_location}") 1228return flask.render_template("not-found.html"), 404 1229 1230if flask.request.method == "GET": 1231repo = git.Repo(server_repo_location) 1232repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1233user = User.query.filter_by(username=flask.session.get("username")).first() 1234 1235return flask.render_template( 1236"repo-prs.html", 1237username=username, 1238repository=repository, 1239repo_data=repo_data, 1240repo=repo, 1241PullRequest=PullRequest, 1242remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1243is_favourite=get_favourite(flask.session.get("username"), username, repository), 1244default_branch=repo_data.default_branch, 1245branches=repo.branches 1246) 1247 1248else: 1249repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1250head = flask.request.form.get("head") 1251head_route = flask.request.form.get("headroute") 1252base = flask.request.form.get("base") 1253 1254if not head and base and head_route: 1255return flask.redirect(".", 400) 1256 1257head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/"))) 1258base_repo = git.Repo(server_repo_location) 1259print(head_repo) 1260 1261if head not in head_repo.branches or base not in base_repo.branches: 1262flask.flash(Markup( 1263"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Bad branch name")), 1264category="error") 1265return flask.redirect(".", 303) 1266 1267head_data = db.session.get(Repo, head_route) 1268if not head_data.visibility: 1269flask.flash(Markup( 1270"<iconify-icon icon='mdi:error'></iconify-icon>" + _( 1271"Head can't be restricted")), 1272category="error") 1273return flask.redirect(".", 303) 1274 1275pull_request = PullRequest(repo_data, head, head_data, base, 1276db.session.get(User, flask.session["username"])) 1277 1278db.session.add(pull_request) 1279db.session.commit() 1280 1281return flask.redirect(".", 303) 1282 1283 1284@repositories.route("/<username>/<repository>/prs/merge", methods=["POST"]) 1285def repository_prs_merge(username, repository): 1286server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1287if not os.path.exists(server_repo_location): 1288app.logger.error(f"Cannot load {server_repo_location}") 1289flask.abort(404) 1290if not (get_visibility(username, repository) or get_permission_level( 1291flask.session.get("username"), username, 1292repository) is not None): 1293flask.abort(403) 1294 1295if not get_permission_level(flask.session.get("username"), username, repository): 1296flask.abort(401) 1297 1298repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1299repo = git.Repo(server_repo_location) 1300id = flask.request.form.get("id") 1301 1302pull_request = db.session.get(PullRequest, id) 1303 1304if pull_request: 1305result = celery_tasks.merge_heads.delay( 1306pull_request.head_route, 1307pull_request.head_branch, 1308pull_request.base_route, 1309pull_request.base_branch, 1310simulate=True 1311) 1312task_result = worker.AsyncResult(result.id) 1313 1314return flask.redirect(f"/task/{result.id}?pr-id={id}", 303) 1315# db.session.delete(pull_request) 1316# db.session.commit() 1317else: 1318flask.abort(400) 1319 1320 1321@repositories.route("/<username>/<repository>/prs/<int:id>/merge") 1322def repository_prs_merge_stage_two(username, repository, id): 1323server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1324if not os.path.exists(server_repo_location): 1325app.logger.error(f"Cannot load {server_repo_location}") 1326flask.abort(404) 1327if not (get_visibility(username, repository) or get_permission_level( 1328flask.session.get("username"), username, 1329repository) is not None): 1330flask.abort(403) 1331 1332if not get_permission_level(flask.session.get("username"), username, repository): 1333flask.abort(401) 1334 1335repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1336repo = git.Repo(server_repo_location) 1337 1338pull_request = db.session.get(PullRequest, id) 1339 1340if pull_request: 1341result = celery_tasks.merge_heads.delay( 1342pull_request.head_route, 1343pull_request.head_branch, 1344pull_request.base_route, 1345pull_request.base_branch, 1346simulate=False 1347) 1348task_result = worker.AsyncResult(result.id) 1349 1350pull_request.state = 1 1351db.session.commit() 1352 1353return flask.redirect(f"/task/{result.id}?pr-id={id}", 303) 1354# db.session.delete(pull_request) 1355else: 1356flask.abort(400) 1357 1358 1359@app.route("/task/<task_id>") 1360def task_monitor(task_id): 1361task_result = worker.AsyncResult(task_id) 1362print(task_result.status) 1363 1364return flask.render_template("task-monitor.html", result=task_result) 1365 1366 1367@repositories.route("/<username>/<repository>/prs/delete", methods=["POST"]) 1368def repository_prs_delete(username, repository): 1369server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1370if not os.path.exists(server_repo_location): 1371app.logger.error(f"Cannot load {server_repo_location}") 1372flask.abort(404) 1373if not (get_visibility(username, repository) or get_permission_level( 1374flask.session.get("username"), username, 1375repository) is not None): 1376flask.abort(403) 1377 1378if not get_permission_level(flask.session.get("username"), username, repository): 1379flask.abort(401) 1380 1381repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1382repo = git.Repo(server_repo_location) 1383id = flask.request.form.get("id") 1384 1385pull_request = db.session.get(PullRequest, id) 1386 1387if pull_request: 1388pull_request.state = 2 1389db.session.commit() 1390 1391return flask.redirect(".", 303) 1392 1393 1394@repositories.route("/<username>/<repository>/settings/") 1395def repository_settings(username, repository): 1396if get_permission_level(flask.session.get("username"), username, repository) != 2: 1397flask.abort(401) 1398 1399return flask.render_template("repo-settings.html", username=username, repository=repository) 1400 1401 1402@app.errorhandler(404) 1403def e404(error): 1404return flask.render_template("not-found.html"), 404 1405 1406 1407@app.errorhandler(401) 1408def e401(error): 1409return flask.render_template("unauthorised.html"), 401 1410 1411 1412@app.errorhandler(403) 1413def e403(error): 1414return flask.render_template("forbidden.html"), 403 1415 1416 1417@app.errorhandler(418) 1418def e418(error): 1419return flask.render_template("teapot.html"), 418 1420 1421 1422@app.errorhandler(405) 1423def e405(error): 1424return flask.render_template("method-not-allowed.html"), 405 1425 1426 1427if __name__ == "__main__": 1428app.run(debug=True, port=8080, host="0.0.0.0") 1429 1430app.register_blueprint(repositories) 1431