app.py
Python script, ASCII text executable
1import os 2import random 3import subprocess 4from functools import wraps 5 6import cairosvg 7import flask 8from flask_sqlalchemy import SQLAlchemy 9import git 10import mimetypes 11import magic 12from flask_bcrypt import Bcrypt 13from markupsafe import escape, Markup 14from flask_migrate import Migrate 15from datetime import datetime 16from enum import Enum 17import shutil 18from PIL import Image 19from cairosvg import svg2png 20import platform 21 22import config 23 24app = flask.Flask(__name__) 25 26from flask_httpauth import HTTPBasicAuth 27 28auth = HTTPBasicAuth() 29 30app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI 31app.config["SECRET_KEY"] = config.DB_PASSWORD 32app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 33db = SQLAlchemy(app) 34bcrypt = Bcrypt(app) 35migrate = Migrate(app, db) 36from models import * 37 38 39def gitCommand(repo, data, *args): 40if not os.path.isdir(repo): 41raise FileNotFoundError("Repo not found") 42env = os.environ.copy() 43 44command = ["git", *args] 45 46proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE, 47stdin=subprocess.PIPE) 48print(command) 49 50if data: 51proc.stdin.write(data) 52 53out, err = proc.communicate() 54return out 55 56 57def onlyChars(string, chars): 58for i in string: 59if i not in chars: 60return False 61return True 62 63 64def getPermissionLevel(loggedIn, username, repository): 65user = User.query.filter_by(username=loggedIn).first() 66repo = Repo.query.filter_by(route=f"/{username}/{repository}").first() 67 68if user and repo: 69permission = RepoAccess.query.filter_by(user=user, repo=repo).first() 70if permission: 71return permission.accessLevel 72 73return None 74 75 76def getVisibility(username, repository): 77repo = Repo.query.filter_by(route=f"/{username}/{repository}").first() 78 79if repo: 80return repo.visibility 81 82return None 83 84 85def getFavourite(loggedIn, username, repository): 86print(loggedIn, username, repository) 87relationship = RepoFavourite.query.filter_by(userUsername=loggedIn, repoRoute=f"/{username}/{repository}").first() 88return relationship 89 90 91import gitHTTP 92import jinjaUtils 93 94 95def humanSize(value, decimals=2, scale=1024, 96units=("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")): 97for unit in units: 98if value < scale: 99break 100value /= scale 101if int(value) == value: 102# do not return decimals, if the value is already round 103return int(value), unit 104return round(value * 10 ** decimals) / 10 ** decimals, unit 105 106 107def guessMIME(path): 108if os.path.isdir(path): 109mimetype = "inode/directory" 110elif magic.from_file(path, mime=True): 111mimetype = magic.from_file(path, mime=True) 112else: 113mimetype = "application/octet-stream" 114return mimetype 115 116 117def convertToHTML(path): 118with open(path, "r") as f: 119contents = f.read() 120return contents 121 122 123repositories = flask.Blueprint("repository", __name__, template_folder="templates/repository/") 124 125 126@app.context_processor 127def default(): 128username = flask.session.get("username") 129 130userObject = User.query.filter_by(username=username).first() 131 132return { 133"loggedInUser": username, 134"userObject": userObject, 135"Notification": Notification, 136"unread": UserNotification.query.filter_by(userUsername=username).filter(UserNotification.attentionLevel > 0).count() 137} 138 139 140@app.route("/") 141def main(): 142return flask.render_template("home.html") 143 144 145@app.route("/about/") 146def about(): 147return flask.render_template("about.html", platform=platform) 148 149 150@app.route("/settings/", methods=["GET", "POST"]) 151def settings(): 152if not flask.session.get("username"): 153flask.abort(401) 154if flask.request.method == "GET": 155user = User.query.filter_by(username=flask.session.get("username")).first() 156 157return flask.render_template("user-settings.html", user=user) 158else: 159user = User.query.filter_by(username=flask.session.get("username")).first() 160 161user.displayName = flask.request.form["displayname"] 162user.URL = flask.request.form["url"] 163user.company = flask.request.form["company"] 164user.companyURL = flask.request.form["companyurl"] 165user.location = flask.request.form["location"] 166user.showMail = flask.request.form.get("showmail", user.showMail) 167 168db.session.commit() 169 170flask.flash(Markup("<iconify-icon icon='mdi:check'></iconify-icon>Settings saved"), category="success") 171return flask.redirect(f"/{flask.session.get('username')}", code=303) 172 173 174@app.route("/favourites/", methods=["GET", "POST"]) 175def favourites(): 176if not flask.session.get("username"): 177flask.abort(401) 178if flask.request.method == "GET": 179relationships = RepoFavourite.query.filter_by(userUsername=flask.session.get("username")) 180 181return flask.render_template("favourites.html", favourites=relationships) 182 183 184@app.route("/notifications/", methods=["GET", "POST"]) 185def notifications(): 186if not flask.session.get("username"): 187flask.abort(401) 188if flask.request.method == "GET": 189return flask.render_template("notifications.html", notifications=UserNotification.query.filter_by(userUsername=flask.session.get("username"))) 190 191 192@app.route("/accounts/", methods=["GET", "POST"]) 193def login(): 194if flask.request.method == "GET": 195return flask.render_template("login.html") 196else: 197if "login" in flask.request.form: 198username = flask.request.form["username"] 199password = flask.request.form["password"] 200 201user = User.query.filter_by(username=username).first() 202 203if user and bcrypt.check_password_hash(user.passwordHashed, password): 204flask.session["username"] = user.username 205flask.flash( 206Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged in as {username}"), 207category="success") 208return flask.redirect("/", code=303) 209elif not user: 210flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>User not found"), 211category="alert") 212return flask.render_template("login.html") 213else: 214flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>Invalid password"), 215category="error") 216return flask.render_template("login.html") 217if "signup" in flask.request.form: 218username = flask.request.form["username"] 219password = flask.request.form["password"] 220password2 = flask.request.form["password2"] 221email = flask.request.form.get("email") 222email2 = flask.request.form.get("email2") # repeat email is a honeypot 223name = flask.request.form.get("name") 224 225if not onlyChars(username, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): 226flask.flash(Markup( 227"<iconify-icon icon='mdi:account-error'></iconify-icon>Usernames may only contain Latin alphabet, numbers, '-' and '_'"), 228category="error") 229return flask.render_template("login.html") 230 231if username in config.RESERVED_NAMES: 232flask.flash( 233Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"), 234category="error") 235return flask.render_template("login.html") 236 237userCheck = User.query.filter_by(username=username).first() 238if userCheck: 239flask.flash( 240Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"), 241category="error") 242return flask.render_template("login.html") 243 244if password2 != password: 245flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>Make sure the passwords match"), 246category="error") 247return flask.render_template("login.html") 248 249user = User(username, password, email, name) 250db.session.add(user) 251db.session.commit() 252flask.session["username"] = user.username 253flask.flash(Markup( 254f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully created and logged in as {username}"), 255category="success") 256return flask.redirect("/", code=303) 257 258 259@app.route("/newrepo/", methods=["GET", "POST"]) 260def newRepo(): 261if not flask.session.get("username"): 262flask.abort(401) 263if flask.request.method == "GET": 264return flask.render_template("new-repo.html") 265else: 266name = flask.request.form["name"] 267visibility = int(flask.request.form["visibility"]) 268 269if not onlyChars(name, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): 270flask.flash(Markup( 271"<iconify-icon icon='mdi:error'></iconify-icon>Repository names may only contain Latin alphabet, numbers, '-' and '_'"), 272category="error") 273return flask.render_template("new-repo.html") 274 275user = User.query.filter_by(username=flask.session.get("username")).first() 276 277repo = Repo(user, name, visibility) 278db.session.add(repo) 279db.session.commit() 280 281if not os.path.exists(os.path.join(config.REPOS_PATH, repo.route)): 282subprocess.run(["git", "init", repo.name], 283cwd=os.path.join(config.REPOS_PATH, flask.session.get("username"))) 284 285flask.flash(Markup(f"<iconify-icon icon='mdi:folder'></iconify-icon>Successfully created repository {name}"), 286category="success") 287return flask.redirect(repo.route, code=303) 288 289 290@app.route("/logout") 291def logout(): 292flask.session.clear() 293flask.flash(Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged out"), category="info") 294return flask.redirect("/", code=303) 295 296 297@app.route("/<username>/", methods=["GET", "POST"]) 298def userProfile(username): 299oldRelationship = UserFollow.query.filter_by(followerUsername=flask.session.get("username"), followedUsername=username).first() 300if flask.request.method == "GET": 301user = User.query.filter_by(username=username).first() 302match flask.request.args.get("action"): 303case "repositories": 304repos = Repo.query.filter_by(ownerName=username, visibility=2) 305return flask.render_template("user-profile-repositories.html", user=user, repos=repos, relationship=oldRelationship) 306case "followers": 307return flask.render_template("user-profile-followers.html", user=user, repos=repos, relationship=oldRelationship) 308case _: 309return flask.render_template("user-profile-overview.html", user=user, relationship=oldRelationship) 310 311elif flask.request.method == "POST": 312match flask.request.args.get("action"): 313case "follow": 314if oldRelationship: 315db.session.delete(oldRelationship) 316else: 317relationship = UserFollow( 318flask.session.get("username"), 319username 320) 321db.session.add(relationship) 322 323db.session.commit() 324return flask.redirect("?", code=303) 325 326 327@app.route("/<username>/<repository>/") 328def repositoryIndex(username, repository): 329return flask.redirect("./tree", code=302) 330 331 332@app.route("/info/<username>/avatar") 333def userAvatar(username): 334serverUserdataLocation = os.path.join(config.USERDATA_PATH, username) 335 336if not os.path.exists(serverUserdataLocation): 337return flask.render_template("not-found.html"), 404 338 339return flask.send_from_directory(serverUserdataLocation, "avatar.png") 340 341 342@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>") 343def repositoryRaw(username, repository, branch, subpath): 344if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 345repository) is not None): 346flask.abort(403) 347 348serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 349 350app.logger.info(f"Loading {serverRepoLocation}") 351 352if not os.path.exists(serverRepoLocation): 353app.logger.error(f"Cannot load {serverRepoLocation}") 354return flask.render_template("not-found.html"), 404 355 356repo = git.Repo(serverRepoLocation) 357repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 358if not repoData.defaultBranch: 359if repo.heads: 360repoData.defaultBranch = repo.heads[0].name 361else: 362return flask.render_template("empty.html", 363remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 364if not branch: 365branch = repoData.defaultBranch 366return flask.redirect(f"./{branch}", code=302) 367 368if branch.startswith("tag:"): 369ref = f"tags/{branch[4:]}" 370elif branch.startswith("~"): 371ref = branch[1:] 372else: 373ref = f"heads/{branch}" 374 375ref = ref.replace("~", "/") # encode slashes for URL support 376 377try: 378repo.git.checkout("-f", ref) 379except git.exc.GitCommandError: 380return flask.render_template("not-found.html"), 404 381 382return flask.send_from_directory(config.REPOS_PATH, os.path.join(username, repository, subpath)) 383 384 385@repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""}) 386@repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""}) 387@repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>") 388def repositoryTree(username, repository, branch, subpath): 389if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 390repository) is not None): 391flask.abort(403) 392 393serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 394 395app.logger.info(f"Loading {serverRepoLocation}") 396 397if not os.path.exists(serverRepoLocation): 398app.logger.error(f"Cannot load {serverRepoLocation}") 399return flask.render_template("not-found.html"), 404 400 401repo = git.Repo(serverRepoLocation) 402repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 403if not repoData.defaultBranch: 404if repo.heads: 405repoData.defaultBranch = repo.heads[0].name 406else: 407return flask.render_template("empty.html", 408remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 409if not branch: 410branch = repoData.defaultBranch 411return flask.redirect(f"./{branch}", code=302) 412 413if branch.startswith("tag:"): 414ref = f"tags/{branch[4:]}" 415elif branch.startswith("~"): 416ref = branch[1:] 417else: 418ref = f"heads/{branch}" 419 420ref = ref.replace("~", "/") # encode slashes for URL support 421 422try: 423repo.git.checkout("-f", ref) 424except git.exc.GitCommandError: 425return flask.render_template("not-found.html"), 404 426 427branches = repo.heads 428 429allRefs = [] 430for ref in repo.heads: 431allRefs.append((ref, "head")) 432for ref in repo.tags: 433allRefs.append((ref, "tag")) 434 435if os.path.isdir(os.path.join(serverRepoLocation, subpath)): 436files = [] 437blobs = [] 438 439for entry in os.listdir(os.path.join(serverRepoLocation, subpath)): 440if not os.path.basename(entry) == ".git": 441files.append(os.path.join(subpath, entry)) 442 443infos = [] 444 445for file in files: 446path = os.path.join(serverRepoLocation, file) 447mimetype = guessMIME(path) 448 449text = gitCommand(serverRepoLocation, None, "log", "--format='%H\n'", file).decode() 450 451sha = text.split("\n")[0] 452identifier = f"/{username}/{repository}/{sha}" 453lastCommit = Commit.query.filter_by(identifier=identifier).first() 454 455info = { 456"name": os.path.basename(file), 457"serverPath": path, 458"relativePath": file, 459"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file), 460"size": humanSize(os.path.getsize(path)), 461"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}", 462"commit": lastCommit, 463"shaSize": 7, 464} 465 466specialIcon = config.matchIcon(os.path.basename(file)) 467if specialIcon: 468info["icon"] = specialIcon 469elif os.path.isdir(path): 470info["icon"] = config.folderIcon 471elif mimetypes.guess_type(path)[0] in config.fileIcons: 472info["icon"] = config.fileIcons[mimetypes.guess_type(path)[0]] 473else: 474info["icon"] = config.unknownIcon 475 476if os.path.isdir(path): 477infos.insert(0, info) 478else: 479infos.append(info) 480 481return flask.render_template( 482"repo-tree.html", 483username=username, 484repository=repository, 485files=infos, 486subpath=os.path.join("/", subpath), 487branches=allRefs, 488current=branch, 489remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 490isFavourite=getFavourite(flask.session.get("username"), username, repository) 491) 492else: 493path = os.path.join(serverRepoLocation, subpath) 494 495if not os.path.exists(path): 496return flask.render_template("not-found.html"), 404 497 498mimetype = guessMIME(path) 499mode = mimetype.split("/", 1)[0] 500size = humanSize(os.path.getsize(path)) 501 502specialIcon = config.matchIcon(os.path.basename(path)) 503if specialIcon: 504icon = specialIcon 505elif os.path.isdir(path): 506icon = config.folderIcon 507elif mimetypes.guess_type(path)[0] in config.fileIcons: 508icon = config.fileIcons[mimetypes.guess_type(path)[0]] 509else: 510icon = config.unknownIcon 511 512contents = None 513if mode == "text": 514contents = convertToHTML(path) 515 516return flask.render_template( 517"repo-file.html", 518username=username, 519repository=repository, 520file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath), 521branches=allRefs, 522current=branch, 523mode=mode, 524mimetype=mimetype, 525detailedtype=magic.from_file(path), 526size=size, 527icon=icon, 528subpath=os.path.join("/", subpath), 529basename=os.path.basename(path), 530contents=contents, 531remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 532isFavourite=getFavourite(flask.session.get("username"), username, repository) 533) 534 535 536@repositories.route("/<username>/<repository>/forum/") 537def repositoryForum(username, repository): 538if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 539repository) is not None): 540flask.abort(403) 541 542serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 543 544app.logger.info(f"Loading {serverRepoLocation}") 545 546if not os.path.exists(serverRepoLocation): 547app.logger.error(f"Cannot load {serverRepoLocation}") 548return flask.render_template("not-found.html"), 404 549 550repo = git.Repo(serverRepoLocation) 551repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 552user = User.query.filter_by(username=flask.session.get("username")).first() 553relationships = RepoAccess.query.filter_by(repo=repoData) 554userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 555 556return flask.render_template( 557"repo-forum.html", 558username=username, 559repository=repository, 560repoData=repoData, 561relationships=relationships, 562repo=repo, 563userRelationship=userRelationship, 564Post=Post, 565remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 566isFavourite=getFavourite(flask.session.get("username"), username, repository) 567) 568 569 570@repositories.route("/<username>/<repository>/forum/new", methods=["POST"]) 571def repositoryForumAdd(username, repository): 572if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 573repository) is not None): 574flask.abort(403) 575 576serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 577 578app.logger.info(f"Loading {serverRepoLocation}") 579 580if not os.path.exists(serverRepoLocation): 581app.logger.error(f"Cannot load {serverRepoLocation}") 582return flask.render_template("not-found.html"), 404 583 584repo = git.Repo(serverRepoLocation) 585repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 586user = User.query.filter_by(username=flask.session.get("username")).first() 587relationships = RepoAccess.query.filter_by(repo=repoData) 588userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 589 590post = Post(user, repoData, None, flask.request.form["subject"], flask.request.form["message"]) 591 592db.session.add(post) 593db.session.commit() 594 595return flask.redirect(flask.url_for(".repositoryForumThread", username=username, repository=repository, postID=post.number), code=303) 596 597 598@repositories.route("/<username>/<repository>/forum/<int:postID>") 599def repositoryForumThread(username, repository, postID): 600if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 601repository) is not None): 602flask.abort(403) 603 604serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 605 606app.logger.info(f"Loading {serverRepoLocation}") 607 608if not os.path.exists(serverRepoLocation): 609app.logger.error(f"Cannot load {serverRepoLocation}") 610return flask.render_template("not-found.html"), 404 611 612repo = git.Repo(serverRepoLocation) 613repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 614user = User.query.filter_by(username=flask.session.get("username")).first() 615relationships = RepoAccess.query.filter_by(repo=repoData) 616userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 617 618return flask.render_template( 619"repo-forum-thread.html", 620username=username, 621repository=repository, 622repoData=repoData, 623relationships=relationships, 624repo=repo, 625userRelationship=userRelationship, 626Post=Post, 627postID=postID, 628maxPostNesting=4, 629remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 630isFavourite=getFavourite(flask.session.get("username"), username, repository) 631) 632 633 634@repositories.route("/<username>/<repository>/forum/<int:postID>/reply", methods=["POST"]) 635def repositoryForumReply(username, repository, postID): 636if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 637repository) is not None): 638flask.abort(403) 639 640serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 641 642app.logger.info(f"Loading {serverRepoLocation}") 643 644if not os.path.exists(serverRepoLocation): 645app.logger.error(f"Cannot load {serverRepoLocation}") 646return flask.render_template("not-found.html"), 404 647 648repo = git.Repo(serverRepoLocation) 649repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 650user = User.query.filter_by(username=flask.session.get("username")).first() 651relationships = RepoAccess.query.filter_by(repo=repoData) 652userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 653if not user: 654flask.abort(401) 655 656parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first() 657post = Post(user, repoData, parent, flask.request.form["subject"], flask.request.form["message"]) 658 659db.session.add(post) 660post.updateDate() 661db.session.commit() 662 663return flask.redirect(flask.url_for(".repositoryForumThread", username=username, repository=repository, postID=postID), code=303) 664 665 666@app.route("/<username>/<repository>/forum/<int:postID>/voteup", defaults={"score": 1}) 667@app.route("/<username>/<repository>/forum/<int:postID>/votedown", defaults={"score": -1}) 668@app.route("/<username>/<repository>/forum/<int:postID>/votes", defaults={"score": 0}) 669def repositoryForumVote(username, repository, postID, score): 670if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 671repository) is not None): 672flask.abort(403) 673 674serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 675 676app.logger.info(f"Loading {serverRepoLocation}") 677 678if not os.path.exists(serverRepoLocation): 679app.logger.error(f"Cannot load {serverRepoLocation}") 680return flask.render_template("not-found.html"), 404 681 682repo = git.Repo(serverRepoLocation) 683repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 684user = User.query.filter_by(username=flask.session.get("username")).first() 685relationships = RepoAccess.query.filter_by(repo=repoData) 686userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 687if not user: 688flask.abort(401) 689 690post = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first() 691 692if score: 693oldRelationship = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first() 694if oldRelationship: 695if score == oldRelationship.voteScore: 696db.session.delete(oldRelationship) 697post.voteSum -= oldRelationship.voteScore 698else: 699post.voteSum -= oldRelationship.voteScore 700post.voteSum += score 701oldRelationship.voteScore = score 702else: 703relationship = PostVote(user, post, score) 704post.voteSum += score 705db.session.add(relationship) 706 707db.session.commit() 708 709userVote = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first() 710response = flask.make_response(str(post.voteSum) + " " + str(userVote.voteScore if userVote else 0)) 711response.content_type = "text/plain" 712 713return response 714 715 716@app.route("/<username>/<repository>/favourite") 717def repositoryFavourite(username, repository): 718if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 719repository) is not None): 720flask.abort(403) 721 722serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 723 724app.logger.info(f"Loading {serverRepoLocation}") 725 726if not os.path.exists(serverRepoLocation): 727app.logger.error(f"Cannot load {serverRepoLocation}") 728return flask.render_template("not-found.html"), 404 729 730repo = git.Repo(serverRepoLocation) 731repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 732user = User.query.filter_by(username=flask.session.get("username")).first() 733relationships = RepoAccess.query.filter_by(repo=repoData) 734userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 735if not user: 736flask.abort(401) 737 738oldRelationship = RepoFavourite.query.filter_by(userUsername=user.username, repoRoute=repoData.route).first() 739if oldRelationship: 740db.session.delete(oldRelationship) 741else: 742relationship = RepoFavourite(user, repoData) 743db.session.add(relationship) 744 745db.session.commit() 746 747return flask.redirect(flask.url_for("favourites"), code=303) 748 749 750@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"]) 751def repositoryUsers(username, repository): 752if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 753repository) is not None): 754flask.abort(403) 755 756serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 757 758app.logger.info(f"Loading {serverRepoLocation}") 759 760if not os.path.exists(serverRepoLocation): 761app.logger.error(f"Cannot load {serverRepoLocation}") 762return flask.render_template("not-found.html"), 404 763 764repo = git.Repo(serverRepoLocation) 765repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 766user = User.query.filter_by(username=flask.session.get("username")).first() 767relationships = RepoAccess.query.filter_by(repo=repoData) 768userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 769 770if flask.request.method == "GET": 771return flask.render_template( 772"repo-users.html", 773username=username, 774repository=repository, 775repoData=repoData, 776relationships=relationships, 777repo=repo, 778userRelationship=userRelationship, 779remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 780isFavourite=getFavourite(flask.session.get("username"), username, repository) 781) 782else: 783if getPermissionLevel(flask.session.get("username"), username, repository) != 2: 784flask.abort(401) 785 786if flask.request.form.get("new-username"): 787# Create new relationship 788newUser = User.query.filter_by(username=flask.request.form.get("new-username")).first() 789relationship = RepoAccess(newUser, repoData, flask.request.form.get("new-level")) 790db.session.add(relationship) 791db.session.commit() 792if flask.request.form.get("update-username"): 793# Create new relationship 794updatedUser = User.query.filter_by(username=flask.request.form.get("update-username")).first() 795relationship = RepoAccess.query.filter_by(repo=repoData, user=updatedUser).first() 796if flask.request.form.get("update-level") == -1: 797relationship.delete() 798else: 799relationship.accessLevel = flask.request.form.get("update-level") 800db.session.commit() 801 802return flask.redirect(app.url_for(".repositoryUsers", username=username, repository=repository)) 803 804 805@repositories.route("/<username>/<repository>/branches/") 806def repositoryBranches(username, repository): 807if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 808repository) is not None): 809flask.abort(403) 810 811serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 812 813app.logger.info(f"Loading {serverRepoLocation}") 814 815if not os.path.exists(serverRepoLocation): 816app.logger.error(f"Cannot load {serverRepoLocation}") 817return flask.render_template("not-found.html"), 404 818 819repo = git.Repo(serverRepoLocation) 820repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 821 822return flask.render_template( 823"repo-branches.html", 824username=username, 825repository=repository, 826repoData=repoData, 827repo=repo, 828remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 829isFavourite=getFavourite(flask.session.get("username"), username, repository) 830) 831 832 833@repositories.route("/<username>/<repository>/log/", defaults={"branch": None}) 834@repositories.route("/<username>/<repository>/log/<branch>/") 835def repositoryLog(username, repository, branch): 836if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 837repository) is not None): 838flask.abort(403) 839 840serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 841 842app.logger.info(f"Loading {serverRepoLocation}") 843 844if not os.path.exists(serverRepoLocation): 845app.logger.error(f"Cannot load {serverRepoLocation}") 846return flask.render_template("not-found.html"), 404 847 848repo = git.Repo(serverRepoLocation) 849repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 850if not repoData.defaultBranch: 851if repo.heads: 852repoData.defaultBranch = repo.heads[0].name 853else: 854return flask.render_template("empty.html", 855remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 856if not branch: 857branch = repoData.defaultBranch 858return flask.redirect(f"./{branch}", code=302) 859 860if branch.startswith("tag:"): 861ref = f"tags/{branch[4:]}" 862elif branch.startswith("~"): 863ref = branch[1:] 864else: 865ref = f"heads/{branch}" 866 867ref = ref.replace("~", "/") # encode slashes for URL support 868 869try: 870repo.git.checkout("-f", ref) 871except git.exc.GitCommandError: 872return flask.render_template("not-found.html"), 404 873 874branches = repo.heads 875 876allRefs = [] 877for ref in repo.heads: 878allRefs.append((ref, "head")) 879for ref in repo.tags: 880allRefs.append((ref, "tag")) 881 882commitList = [f"/{username}/{repository}/{sha}" for sha in gitCommand(serverRepoLocation, None, "log", "--format='%H'").decode().split("\n")] 883 884commits = Commit.query.filter(Commit.identifier.in_(commitList)) 885 886return flask.render_template( 887"repo-log.html", 888username=username, 889repository=repository, 890branches=allRefs, 891current=branch, 892repoData=repoData, 893repo=repo, 894commits=commits, 895remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 896isFavourite=getFavourite(flask.session.get("username"), username, repository) 897) 898 899 900@repositories.route("/<username>/<repository>/settings/") 901def repositorySettings(username, repository): 902if getPermissionLevel(flask.session.get("username"), username, repository) != 2: 903flask.abort(401) 904 905return flask.render_template("repo-settings.html", username=username, repository=repository) 906 907 908@app.errorhandler(404) 909def e404(error): 910return flask.render_template("not-found.html"), 404 911 912 913@app.errorhandler(401) 914def e401(error): 915return flask.render_template("unauthorised.html"), 401 916 917 918@app.errorhandler(403) 919def e403(error): 920return flask.render_template("forbidden.html"), 403 921 922 923@app.errorhandler(418) 924def e418(error): 925return flask.render_template("teapot.html"), 418 926 927 928@app.errorhandler(405) 929def e405(error): 930return flask.render_template("method-not-allowed.html"), 405 931 932 933if __name__ == "__main__": 934app.run(debug=True, port=8080, host="0.0.0.0") 935 936 937app.register_blueprint(repositories) 938