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