roundabout,
created on Sunday, 17 December 2023, 19:41:04 (1702842064),
received on Wednesday, 31 July 2024, 06:54:39 (1722408879)
Author identity: vlad <vlad.muntoiu@gmail.com>
a6d3365a21fa37b09f54fd8260ac0b60ea635550
app.py
@@ -78,6 +78,23 @@ with app.app_context():
self.accessLevel = level class PostVote(db.Model): id = db.Column(db.Integer, primary_key=True) userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) postIdentifier = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=False) voteScore = db.Column(db.SmallInteger(), nullable=False) user = db.relationship("User", back_populates="votes") post = db.relationship("Post", back_populates="votes") __table_args__ = (db.UniqueConstraint("userUsername", "postIdentifier", name="_user_post_uc"),) def __init__(self, user, post, score): self.userUsername = user.username self.postIdentifier = post.identifier self.voteScore = score class User(db.Model): username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True) displayName = db.Column(db.Unicode(128), unique=False, nullable=True)
@@ -93,6 +110,7 @@ with app.app_context():
repositories = db.relationship("Repo", back_populates="owner") repoAccess = db.relationship("RepoAccess", back_populates="user") votes = db.relationship("PostVote", back_populates="user")commits = db.relationship("Commit", back_populates="owner") posts = db.relationship("Post", back_populates="owner")
@@ -163,7 +181,7 @@ with app.app_context():
owner = db.relationship("User", back_populates="commits") def __init__(self, sha, owner, repo, date, message, ownerIdentity): self.identifier = f"/{owner.username}/{repo.name}/{sha}"self.identifier = f"{repo.route}/{sha}"self.sha = sha self.repoName = repo.route self.repo = repo
@@ -176,14 +194,17 @@ with app.app_context():
class Post(db.Model): identifier = db.Column(db.String(109), unique=True, nullable=False, primary_key=True) number = db.Column(db.String(109), nullable=False)number = db.Column(db.Integer, nullable=False)repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) votes = db.relationship("PostVote", back_populates="post") voteSum = db.Column(db.Integer, nullable=False, default=0)parentID = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True) state = db.Column(db.SmallInteger, nullable=True, default=1) date = db.Column(db.DateTime, default=datetime.now) lastUpdated = db.Column(db.DateTime, default=datetime.now)subject = db.Column(db.Unicode(384)) message = db.Column(db.UnicodeText) repo = db.relationship("Repo", back_populates="posts")
@@ -192,7 +213,7 @@ with app.app_context():
children = db.relationship("Post", back_populates="parent", remote_side="Post.parentID") def __init__(self, owner, repo, parent, subject, message): self.identifier = f"/{owner.username}/{repo.name}/{repo.lastPostID}"self.identifier = f"{repo.route}/{repo.lastPostID}"self.number = repo.lastPostID self.repoName = repo.route self.repo = repo
@@ -203,6 +224,12 @@ with app.app_context():
self.parent = parent repo.lastPostID += 1 def updateDate(self): self.lastUpdated = datetime.now() with db.session.no_autoflush: if self.parent is not None: self.parent.updateDate() def getPermissionLevel(loggedIn, username, repository): user = User.query.filter_by(username=loggedIn).first()
@@ -419,7 +446,7 @@ def repositoryRaw(username, repository, branch, subpath):
repository) is not None): flask.abort(403) serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)app.logger.info(f"Loading {serverRepoLocation}")
@@ -474,7 +501,7 @@ def repositoryTree(username, repository, branch, subpath):
repository) is not None): flask.abort(403) serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)app.logger.info(f"Loading {serverRepoLocation}")
@@ -619,7 +646,7 @@ def repositoryForum(username, repository):
repository) is not None): flask.abort(403) serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)app.logger.info(f"Loading {serverRepoLocation}")
@@ -629,20 +656,48 @@ def repositoryForum(username, repository):
repo = git.Repo(serverRepoLocation) repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() user = User.query.filter_by(username=username).first()user = User.query.filter_by(username=flask.session.get("username")).first()relationships = RepoAccess.query.filter_by(repo=repoData) userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() return flask.render_template("repo-forum.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship, Post=Post) @app.route("/<username>/<repository>/forum/<postID>")@app.route("/<username>/<repository>/forum/new", methods=["POST"]) def repositoryForumAdd(username, repository): if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, repository) is not None): flask.abort(403) serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) app.logger.info(f"Loading {serverRepoLocation}") if not os.path.exists(serverRepoLocation): app.logger.error(f"Cannot load {serverRepoLocation}") return flask.render_template("not-found.html"), 404 repo = git.Repo(serverRepoLocation) repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() user = User.query.filter_by(username=flask.session.get("username")).first() relationships = RepoAccess.query.filter_by(repo=repoData) userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() post = Post(user, repoData, None, flask.request.form["subject"], flask.request.form["message"]) db.session.add(post) db.session.commit() return flask.redirect(flask.url_for("repositoryForumThread", username=username, repository=repository, postID=post.number), code=303) @app.route("/<username>/<repository>/forum/<int:postID>")def repositoryForumThread(username, repository, postID): if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, repository) is not None): flask.abort(403) serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)app.logger.info(f"Loading {serverRepoLocation}")
@@ -652,20 +707,102 @@ def repositoryForumThread(username, repository, postID):
repo = git.Repo(serverRepoLocation) repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() user = User.query.filter_by(username=username).first()user = User.query.filter_by(username=flask.session.get("username")).first()relationships = RepoAccess.query.filter_by(repo=repoData) userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() return flask.render_template("repo-forum-thread.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship, Post=Post, postID=postID, maxPostNesting=4) @app.route("/<username>/<repository>/forum/<int:postID>/reply", methods=["POST"]) def repositoryForumReply(username, repository, postID): if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, repository) is not None): flask.abort(403) serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) app.logger.info(f"Loading {serverRepoLocation}") if not os.path.exists(serverRepoLocation): app.logger.error(f"Cannot load {serverRepoLocation}") return flask.render_template("not-found.html"), 404 repo = git.Repo(serverRepoLocation) repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() user = User.query.filter_by(username=flask.session.get("username")).first() relationships = RepoAccess.query.filter_by(repo=repoData) userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() if not user: flask.abort(401) parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first() post = Post(user, repoData, parent, flask.request.form["subject"], flask.request.form["message"]) db.session.add(post) post.updateDate() db.session.commit() return flask.redirect(flask.url_for("repositoryForumThread", username=username, repository=repository, postID=postID), code=303) @app.route("/<username>/<repository>/forum/<int:postID>/voteup", defaults={"score": 1}) @app.route("/<username>/<repository>/forum/<int:postID>/votedown", defaults={"score": -1}) @app.route("/<username>/<repository>/forum/<int:postID>/votes", defaults={"score": 0}) def repositoryForumVote(username, repository, postID, score): if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, repository) is not None): flask.abort(403) serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) app.logger.info(f"Loading {serverRepoLocation}") if not os.path.exists(serverRepoLocation): app.logger.error(f"Cannot load {serverRepoLocation}") return flask.render_template("not-found.html"), 404 repo = git.Repo(serverRepoLocation) repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() user = User.query.filter_by(username=flask.session.get("username")).first() relationships = RepoAccess.query.filter_by(repo=repoData) userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() if not user: flask.abort(401) post = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first() if score: oldRelationship = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first() if oldRelationship: if score == oldRelationship.voteScore: db.session.delete(oldRelationship) post.voteSum -= oldRelationship.voteScore else: post.voteSum -= oldRelationship.voteScore post.voteSum += score oldRelationship.voteScore = score else: relationship = PostVote(user, post, score) post.voteSum += score db.session.add(relationship) db.session.commit() userVote = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first() response = flask.make_response(str(post.voteSum) + " " + str(userVote.voteScore if userVote else 0)) response.content_type = "text/plain" return response @app.route("/<username>/<repository>/users/", methods=["GET", "POST"]) def repositoryUsers(username, repository): if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, repository) is not None): flask.abort(403) serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)app.logger.info(f"Loading {serverRepoLocation}")
@@ -710,7 +847,7 @@ def repositoryBranches(username, repository):
repository) is not None): flask.abort(403) serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)app.logger.info(f"Loading {serverRepoLocation}")
@@ -730,7 +867,7 @@ def repositoryLog(username, repository):
repository) is not None): flask.abort(403) serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)app.logger.info(f"Loading {serverRepoLocation}")
static/voting.js
@@ -0,0 +1,24 @@
function updateButtons(postID, score) { document.getElementById(postID + "-voteup").classList.add("button-flat"); document.getElementById(postID + "-votedown").classList.add("button-flat"); if(score == 1) { document.getElementById(postID + "-voteup").classList.remove("button-flat"); } else if(score == -1) { document.getElementById(postID + "-votedown").classList.remove("button-flat"); } } async function vote(postID, score) { if(score === 1) { action = "voteup"; } else if(score === -1) { action = "votedown"; } else { action = "votes"; } const response = await fetch(postID + "/" + action); const info = await response.text(); document.getElementById(postID + "-vote").innerText = info.split(" ")[0]; updateButtons(postID, info.split(" ")[1]) }
templates/post.html
@@ -19,13 +19,41 @@
<p> {{ post.message }} </p> {% if loggedInUser %} <x-buttonbox class="box-center"> <button onclick="vote({{ post.number }}, 1);" class="button-flat big-button" id="{{ post.number }}-voteup"><iconify-icon icon="mdi:arrow-up"></iconify-icon></button> <span style="font-size: 1.5em;" id="{{ post.number }}-vote">{{ post.voteSum }}</span> <button onclick="vote({{ post.number }}, -1);" class="button-flat big-button" id="{{ post.number }}-votedown"><iconify-icon icon="mdi:arrow-down"></iconify-icon></button> <script>vote({{ post.number }}, 0);</script> </x-buttonbox> {% endif %}</section> </article> </dt> {% if loggedInUser %} <dd> <details> <summary>Reply</summary> <form method="POST" action="{{ post.number }}/reply"> <x-vbox> <x-vbox class="nopad"> <label for="{{ post.number }}-subject">Subject</label> <input id="{{ post.number }}-subject" name="subject" value="Re: {{ post.subject }}" required> </x-vbox> <textarea name="message" style="box-sizing: border-box;" rows="8" required></textarea> <x-buttonbox> <button type="submit">Submit</button> </x-buttonbox> </x-vbox> </form> </details> </dd> {% endif %}{% set level = level + 1 %} {% if level <= maxPostNesting %} {% if post.children %} {% for post in Post.query.filter_by(repo=repoData, parent=parent) %}{% for post in Post.query.filter_by(parent=post).order_by(Post.date) %}<dd>{% include "post.html" %}</dd> {% endfor %} {% endif %}
templates/repo-forum-thread.html
@@ -1,12 +1,13 @@
{% extends "repo.html" %} {% set parent = Post.query.filter_by(repo=repoData, number=postID).first() %}{% block title %} Forum of {{ username }}/{{ repository }}{{ parent.subject }} in {{ username }}/{{ repository }}{% endblock %} {% block content %} <script src="/static/voting.js"></script><x-vbox> <x-frame style="--width: 896px;" class="flexible-space"> <x-vbox> {% set parent = Post.query.filter_by(repo=repoData, number=postID).first() %}{% set post = parent %} {% set level = 0 %} {% include "post.html" %}
templates/repo-forum.html
@@ -6,13 +6,25 @@
<x-vbox> <x-frame style="--width: 896px;" class="flexible-space"> <x-vbox> {% for post in Post.query.filter_by(repo=repoData, parent=none) %}<form method="POST" action="new"> <x-vbox> <x-vbox class="nopad"> <label for="subject">Subject</label> <input id="subject" name="subject" required> </x-vbox> <textarea name="message" style="box-sizing: border-box;" rows="8" required></textarea> <x-buttonbox> <button type="submit">Add new topic</button> </x-buttonbox> </x-vbox> </form> {% for post in Post.query.filter_by(repo=repoData, parent=none).order_by(Post.lastUpdated.desc()) %}<article class="card card-horizontal"> <figure class="branch-icon"> <iconify-icon icon="mdi:note"></iconify-icon> </figure> <section class="card-main"> <h3><a href="{{ post.number }}">{{ post.subject }}</a></h3><h3><a href="{{ post.number }}">{{ post.subject }}</a> ({{ post.children | length }})</h3><p><a href="/{{ post.owner.username }}">{{ post.owner.username }}</a></p> </section> </article>