roundabout,
created on Wednesday, 8 November 2023, 17:00:54 (1699462854),
received on Wednesday, 31 July 2024, 06:54:38 (1722408878)
Author identity: vlad <vlad.muntoiu@gmail.com>
b1a4746c23f8359ed5f890860b715072a8fc3cec
config.py
@@ -5,10 +5,14 @@ load_dotenv("secrets.env")
DB_PASSWORD = os.environ.get("DB_PASSWORD") REPOS_PATH = "./repos" USERDATA_PATH = "./userdata" DEFAULT_AVATARS_PATH = "./defaultAvatars"BASE_DOMAIN = "localhost" AVATAR_SIZE = (192, 192) HASHING_ROUNDS = 16 RESERVED_NAMES = ("git", "settings", "logout", "accounts",)RESERVED_NAMES = ("git", "settings", "logout", "accounts", "info",)locking = False
gitHTTP.py
@@ -1,5 +1,6 @@
import uuid from gitme import app, User, db, bcryptfrom gitme import app, User, Repo, db, bcryptimport os import shutil import config
@@ -130,13 +131,19 @@ def gitMakeInfo(username, repository):
def gitInfoRefs(username, repository): serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository)) app.logger.info(f"Loading {serverRepoLocation}") repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()if not os.path.exists(serverRepoLocation): app.logger.error(f"Cannot load {serverRepoLocation}") flask.abort(404) text = "" repo = git.Repo(serverRepoLocation) text = f"{repo.heads[0].commit.hexsha}\tHEAD\n"for i in repo.heads: if i.name == repoData.defaultBranch: text = f"{i.commit.hexsha}\tHEAD\n" breakfor ref in repo.heads: text += f"{ref.commit.hexsha}\trefs/heads/{ref.name}\n"
gitme.py
@@ -1,4 +1,7 @@
import os import random import cairosvgimport flask from flask_sqlalchemy import SQLAlchemy import git
@@ -7,6 +10,11 @@ import magic
from flask_bcrypt import Bcrypt from markupsafe import escape, Markup from flask_migrate import Migrate from datetime import datetime from enum import Enum import shutil from PIL import Image from cairosvg import svg2pngimport config
@@ -33,15 +41,59 @@ def onlyChars(string, chars):
with app.app_context(): class User(db.Model): username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True) displayName = db.Column(db.String(128), unique=False, nullable=True)displayName = db.Column(db.Unicode(128), unique=False, nullable=True) bio = db.Column(db.Unicode(512), unique=False, nullable=True)passwordHashed = db.Column(db.String(60), nullable=False) email = db.Column(db.String(254), nullable=True) company = db.Column(db.Unicode(64), nullable=True) companyURL = db.Column(db.String(256), nullable=True) URL = db.Column(db.String(256), nullable=True) showMail = db.Column(db.Boolean, default=False, nullable=False) location = db.Column(db.Unicode(64), nullable=True) creationDate = db.Column(db.DateTime, default=datetime.utcnow) repositories = db.relationship("Repo", backref="owner")def __init__(self, username, password, email=None, displayName=None): self.username = username self.passwordHashed = bcrypt.generate_password_hash(password, config.HASHING_ROUNDS).decode("utf-8") self.email = email self.displayName = displayName # Create the user's directory if not os.path.exists(os.path.join(config.REPOS_PATH, username)): os.makedirs(os.path.join(config.REPOS_PATH, username)) if not os.path.exists(os.path.join(config.USERDATA_PATH, username)): os.makedirs(os.path.join(config.USERDATA_PATH, username)) avatarName = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH)) if os.path.join(config.DEFAULT_AVATARS_PATH, avatarName).endswith(".svg"): cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatarName), write_to="/tmp/gitme-avatar.png") avatar = Image.open("/tmp/gitme-avatar.png") else: avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatarName)) avatar.thumbnail(config.AVATAR_SIZE) avatar.save(os.path.join(config.USERDATA_PATH, username, "avatar.png")) class Repo(db.Model): route = db.Column(db.String(97), unique=True, nullable=False, primary_key=True) ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) name = db.Column(db.String(64), nullable=False) # owner = db.relationship("User", back_populates="repositories") visibility = db.Column(db.SmallInteger(), nullable=False) info = db.Column(db.Unicode(512), nullable=True) URL = db.Column(db.String(256), nullable=True) creationDate = db.Column(db.DateTime, default=datetime.utcnow) defaultBranch = db.Column(db.String(64), nullable=False, default="master") def __init__(self, owner, name, visibility): self.route = f"/{owner.username}/{name}" self.name = name self.ownerName = owner.username self.owner = owner self.visibility = visibilityimport gitHTTP
@@ -86,8 +138,12 @@ def main():
@app.route("/settings/") def main():return flask.render_template("user-settings.html", title="gitme")def settings(): if not flask.session.get("username"): flask.abort(401) user = User.query.filter_by(username=flask.session.get("username")).first() return flask.render_template("user-settings.html", user=user)@app.route("/accounts/", methods=["GET", "POST"])
@@ -137,7 +193,6 @@ def login():
return flask.render_template("login.html", title="gitme") user = User(username, password, email, name) os.makedirs(os.path.join(config.REPOS_PATH, username))db.session.add(user) db.session.commit() flask.session["username"] = user.username
@@ -154,7 +209,9 @@ def logout():
@app.route("/<username>/") def userProfile(username): return flask.render_template("teapot.html"), 418user = User.query.filter_by(username=username).first() repos = Repo.query.filter_by(ownerName=username, visibility=2) return flask.render_template("user-profile.html", user=user, repos=repos)@app.route("/<username>/<repository>/")
@@ -181,6 +238,16 @@ def repositoryRaw(username, repository, branch, subpath):
return flask.send_from_directory(config.REPOS_PATH, os.path.join(username, repository, subpath)) @app.route("/info/<username>/avatar") def userAvatar(username): serverUserdataLocation = os.path.join(config.USERDATA_PATH, username) if not os.path.exists(serverUserdataLocation): return flask.render_template("not-found.html"), 404 return flask.send_from_directory(serverUserdataLocation, "avatar.png") @app.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""}) @app.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""}) @app.route("/<username>/<repository>/tree/<branch>/<path:subpath>")
@@ -194,8 +261,9 @@ def repositoryTree(username, repository, branch, subpath):
return flask.render_template("not-found.html"), 404 repo = git.Repo(serverRepoLocation) repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()if not branch: branch = repo.heads[0].namebranch = repoData.defaultBranchreturn flask.redirect(f"./{branch}", code=302) try: repo.git.checkout(branch)
static/style.css
@@ -102,4 +102,9 @@ button, input, .button, select {
padding: 8px; gap: 4px; border-radius: 2px; } #username p { font-size: 1.5em; font-weight: 300;}
templates/user-profile.html
@@ -0,0 +1,66 @@
{% extends "default.html" %} {% block title %} {{ user.username }} at gitme {% endblock %} {% block breadcrumbs %} <li><a href="/{{ user.username }}">{{ user.username }}</a></li> {% endblock %} {% block content %} <x-frame style="--width: 768px;"> <x-vbox> <x-hbox style="align-items: center;"> <img src="/info/{{ user.username }}/avatar" class="avatar"> <x-vbox class="nopad" style="flex: 1 0 auto;"> {% if user.displayName and user.displayName != user.username %} <hgroup id="username"> <h1 class="headline">{{ user.displayName }}</h1> <p>{{ user.username }}</p> </hgroup> {% else %} <h1 class="headline">{{ user.username }}</h1> {% endif %} </x-vbox> <div class="flexible-space"></div> <ul class="noindent" style="list-style: none; text-align: end;"> {% if user.URL %} <li><a href="{{ user.URL }}"><x-hbox><iconify-icon icon="ic:outline-web"></iconify-icon>{{ user.URL }}</x-hbox></a></li> {% endif %} {% if user.companyURL %} <li><a href="{{ user.companyURL }}"><x-hbox><iconify-icon icon="ic:baseline-business-center"></iconify-icon>{{ user.company }}</x-hbox></a></li> {% elif user.company %} <li><x-hbox><iconify-icon icon="ic:baseline-business-center"></iconify-icon>{{ user.company }}</x-hbox></li> {% endif %} {% if user.location %} <li><x-hbox><iconify-icon icon="mdi:map-marker-radius"></iconify-icon>{{ user.location }}</x-hbox></li> {% endif %} </ul> </x-hbox> {% if user.bio %} <article class="card" style="flex: 0 1 auto;"> <section class="card-main"> <p> {{ user.bio }} </p> </section> </article> {% endif %} <article class="card" style="flex: 0 1 auto;"> <section class="card-main"> <ul style="list-style: none;" class="noindent"> {% for repo in repos %} <li> <article> <a href="{{ repo.route }}"><h3>{{ repo.name }}</h3></a> <p>{{ repo.info }}</p> </article> </li> {% endfor %} </ul> </section> </article> </x-vbox> </x-frame> {% endblock %}
templates/user-settings.html
@@ -0,0 +1,60 @@
{% extends "default.html" %} {% block title %} gitme user settings {% endblock %} {% block breadcrumbs %} <li><a href="/settings">Settings</a></li> {% endblock %} {% block content %} <x-frame style="--width: 768px;"> <h1>User settings</h1> <article class="card"> <section class="card-main"> <h2>Basic information</h2> <p>Usermame: {{ user.username }}</p> <p>Email address: {{ user.email }}</p> <x-buttonbox> <a class="button" href="password">Change password</a> <a class="button" href="email">Change email</a> </x-buttonbox> </section> </article> <article class="card"> <section class="card-main"> <form method="post"> <h2>Profile</h2> <x-vbox> <x-vbox class="nopad"> <label for="displayname">Friendly name</label> <input id="displayname" name="displayname" value="{% if user.displayName %}{{ user.displayName }}{% endif %}" placeholder="{{ user.username }}"> </x-vbox> <x-vbox class="nopad"> <label for="url">Link to your website</label> <input id="url" name="url" value="{% if user.URL %}{{ user.URL }}{% endif %}" type="url"> </x-vbox> <x-vbox class="nopad"> <x-hbox style="width: 100%; align-items: space-between;"> <x-vbox style="flex-grow: 1;" class="nopad"> <label for="company">Company or school</label> <input id="company" name="companyurl" value="{% if user.company %}{{ user.company }}{% endif %}"> </x-vbox> <x-vbox style="flex-grow: 1;" class="nopad"> <label for="companyurl">Link to the company's website</label> <input id="companyurl" name="companyurl" value="{% if user.companyURL %}{{ user.companyURL }}{% endif %}" type="url"> </x-vbox> </x-hbox> </x-vbox> <x-vbox class="nopad"> <label><input name="showmail" value="{% if user.showMail %}{{ user.showMail }}{% endif %}" type="checkbox">Show email on my profile</label> </x-vbox> <x-vbox class="nopad"> <label for="bio">Bio</label> <textarea id="bio" rows="4">{% if user.bio %}{{ user.bio }}{% endif %}</textarea> </x-vbox> <button type="submit">Update profile</button> </x-vbox> </form> </section> </article> </x-frame> {% endblock %}