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