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