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, bcrypt
from gitme import app, User, Repo, db, bcrypt
import 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"
break
for 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 cairosvg
import 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 svg2png
import 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 = visibility
import 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"), 418
user = 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].name
branch = repoData.defaultBranch
return 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 %}