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_index(): 168return flask.render_template("help.html", faqs=config.faqs) 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 1240) 1241 1242 1243@repositories.route("/<username>/<repository>/prs/", methods=["GET", "POST"]) 1244def repository_prs(username, repository): 1245server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1246if not os.path.exists(server_repo_location): 1247app.logger.error(f"Cannot load {server_repo_location}") 1248flask.abort(404) 1249if not (get_visibility(username, repository) or get_permission_level( 1250flask.session.get("username"), username, 1251repository) is not None): 1252flask.abort(403) 1253 1254app.logger.info(f"Loading {server_repo_location}") 1255 1256if not os.path.exists(server_repo_location): 1257app.logger.error(f"Cannot load {server_repo_location}") 1258return flask.render_template("not-found.html"), 404 1259 1260if flask.request.method == "GET": 1261repo = git.Repo(server_repo_location) 1262repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1263user = User.query.filter_by(username=flask.session.get("username")).first() 1264 1265return flask.render_template( 1266"repo-prs.html", 1267username=username, 1268repository=repository, 1269repo_data=repo_data, 1270repo=repo, 1271PullRequest=PullRequest, 1272remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1273is_favourite=get_favourite(flask.session.get("username"), username, repository), 1274default_branch=repo_data.default_branch, 1275branches=repo.branches 1276) 1277 1278else: 1279repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1280head = flask.request.form.get("head") 1281head_route = flask.request.form.get("headroute") 1282base = flask.request.form.get("base") 1283 1284if not head and base and head_route: 1285return flask.redirect(".", 400) 1286 1287head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/"))) 1288base_repo = git.Repo(server_repo_location) 1289print(head_repo) 1290 1291if head not in head_repo.branches or base not in base_repo.branches: 1292flask.flash(Markup( 1293"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Bad branch name")), 1294category="error") 1295return flask.redirect(".", 303) 1296 1297head_data = db.session.get(Repo, head_route) 1298if not head_data.visibility: 1299flask.flash(Markup( 1300"<iconify-icon icon='mdi:error'></iconify-icon>" + _( 1301"Head can't be restricted")), 1302category="error") 1303return flask.redirect(".", 303) 1304 1305pull_request = PullRequest(repo_data, head, head_data, base, 1306db.session.get(User, flask.session["username"])) 1307 1308db.session.add(pull_request) 1309db.session.commit() 1310 1311return flask.redirect(".", 303) 1312 1313 1314@repositories.route("/<username>/<repository>/prs/merge", methods=["POST"]) 1315def repository_prs_merge(username, repository): 1316server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1317if not os.path.exists(server_repo_location): 1318app.logger.error(f"Cannot load {server_repo_location}") 1319flask.abort(404) 1320if not (get_visibility(username, repository) or get_permission_level( 1321flask.session.get("username"), username, 1322repository) is not None): 1323flask.abort(403) 1324 1325if not get_permission_level(flask.session.get("username"), username, repository): 1326flask.abort(401) 1327 1328repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1329repo = git.Repo(server_repo_location) 1330id = flask.request.form.get("id") 1331 1332pull_request = db.session.get(PullRequest, id) 1333 1334if pull_request: 1335result = celery_tasks.merge_heads.delay( 1336pull_request.head_route, 1337pull_request.head_branch, 1338pull_request.base_route, 1339pull_request.base_branch, 1340simulate=True 1341) 1342task_result = worker.AsyncResult(result.id) 1343 1344return flask.redirect(f"/task/{result.id}?pr-id={id}", 303) 1345# db.session.delete(pull_request) 1346# db.session.commit() 1347else: 1348flask.abort(400) 1349 1350 1351@repositories.route("/<username>/<repository>/prs/<int:id>/merge") 1352def repository_prs_merge_stage_two(username, repository, id): 1353server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1354if not os.path.exists(server_repo_location): 1355app.logger.error(f"Cannot load {server_repo_location}") 1356flask.abort(404) 1357if not (get_visibility(username, repository) or get_permission_level( 1358flask.session.get("username"), username, 1359repository) is not None): 1360flask.abort(403) 1361 1362if not get_permission_level(flask.session.get("username"), username, repository): 1363flask.abort(401) 1364 1365repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1366repo = git.Repo(server_repo_location) 1367 1368pull_request = db.session.get(PullRequest, id) 1369 1370if pull_request: 1371result = celery_tasks.merge_heads.delay( 1372pull_request.head_route, 1373pull_request.head_branch, 1374pull_request.base_route, 1375pull_request.base_branch, 1376simulate=False 1377) 1378task_result = worker.AsyncResult(result.id) 1379 1380pull_request.state = 1 1381db.session.commit() 1382 1383return flask.redirect(f"/task/{result.id}?pr-id={id}", 303) 1384# db.session.delete(pull_request) 1385else: 1386flask.abort(400) 1387 1388 1389@app.route("/task/<task_id>") 1390def task_monitor(task_id): 1391task_result = worker.AsyncResult(task_id) 1392print(task_result.status) 1393 1394return flask.render_template("task-monitor.html", result=task_result) 1395 1396 1397@repositories.route("/<username>/<repository>/prs/delete", methods=["POST"]) 1398def repository_prs_delete(username, repository): 1399server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1400if not os.path.exists(server_repo_location): 1401app.logger.error(f"Cannot load {server_repo_location}") 1402flask.abort(404) 1403if not (get_visibility(username, repository) or get_permission_level( 1404flask.session.get("username"), username, 1405repository) is not None): 1406flask.abort(403) 1407 1408if not get_permission_level(flask.session.get("username"), username, repository): 1409flask.abort(401) 1410 1411repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1412repo = git.Repo(server_repo_location) 1413id = flask.request.form.get("id") 1414 1415pull_request = db.session.get(PullRequest, id) 1416 1417if pull_request: 1418pull_request.state = 2 1419db.session.commit() 1420 1421return flask.redirect(".", 303) 1422 1423 1424@repositories.route("/<username>/<repository>/settings/") 1425def repository_settings(username, repository): 1426if get_permission_level(flask.session.get("username"), username, repository) != 2: 1427flask.abort(401) 1428 1429return flask.render_template("repo-settings.html", username=username, repository=repository) 1430 1431 1432@app.errorhandler(404) 1433def e404(error): 1434return flask.render_template("not-found.html"), 404 1435 1436 1437@app.errorhandler(401) 1438def e401(error): 1439return flask.render_template("unauthorised.html"), 401 1440 1441 1442@app.errorhandler(403) 1443def e403(error): 1444return flask.render_template("forbidden.html"), 403 1445 1446 1447@app.errorhandler(418) 1448def e418(error): 1449return flask.render_template("teapot.html"), 418 1450 1451 1452@app.errorhandler(405) 1453def e405(error): 1454return flask.render_template("method-not-allowed.html"), 405 1455 1456 1457if __name__ == "__main__": 1458app.run(debug=True, port=8080, host="0.0.0.0") 1459 1460app.register_blueprint(repositories) 1461