mirror of
https://github.com/partitioncloud/partitioncloud-server.git
synced 2025-01-23 09:16:25 +01:00
Add base code
This commit is contained in:
parent
241b947f92
commit
1072956d26
4
.gitignore
vendored
4
.gitignore
vendored
@ -5,5 +5,5 @@
|
||||
.vscode
|
||||
|
||||
# data
|
||||
partitioncloud/partitioncloud.db
|
||||
|
||||
instance/partitioncloud.sqlite
|
||||
partitioncloud/partitions
|
||||
|
7
make.sh
7
make.sh
@ -1,12 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
init () {
|
||||
if [ ! -x partitioncloud/partitioncloud.db ]; then
|
||||
mkdir -p "instance"
|
||||
if [ ! -x instance/partitioncloud.sqlite ]; then
|
||||
printf "Souhaitez vous supprimer la base de données existante ? [y/n] "
|
||||
read -r CONFIRMATION
|
||||
fi
|
||||
[[ $CONFIRMATION == y ]] || exit 1
|
||||
sqlite3 "partitioncloud/partitioncloud.db" '.read partitioncloud/schema.sql'
|
||||
sqlite3 "instance/partitioncloud.sqlite" '.read partitioncloud/schema.sql'
|
||||
echo "Base de données initialisée"
|
||||
}
|
||||
|
||||
@ -27,4 +28,4 @@ else
|
||||
usage
|
||||
echo $(type "$1")
|
||||
exit 1
|
||||
fi;
|
||||
fi
|
||||
|
30
partitioncloud/__init__.py
Normal file
30
partitioncloud/__init__.py
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/python3
|
||||
"""
|
||||
Main file
|
||||
"""
|
||||
import os
|
||||
from flask import Flask, render_template, request, send_file, g, redirect
|
||||
|
||||
from . import auth, albums
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
app.config.from_mapping(
|
||||
# a default secret that should be overridden by instance config
|
||||
SECRET_KEY="dev",
|
||||
# store the database in the instance folder
|
||||
DATABASE=os.path.join(app.instance_path, f"{__name__}.sqlite"),
|
||||
)
|
||||
|
||||
app.register_blueprint(auth.bp)
|
||||
app.register_blueprint(albums.bp)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def home():
|
||||
"""Redirect to home"""
|
||||
return redirect("/albums/")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0")
|
138
partitioncloud/albums.py
Normal file
138
partitioncloud/albums.py
Normal file
@ -0,0 +1,138 @@
|
||||
#!/usr/bin/python3
|
||||
"""
|
||||
Albums module
|
||||
"""
|
||||
import os
|
||||
from uuid import uuid4
|
||||
|
||||
from flask import (Blueprint, abort, flash, redirect, render_template, request,
|
||||
send_file, session)
|
||||
|
||||
from .auth import login_required
|
||||
from .db import get_db
|
||||
|
||||
bp = Blueprint("albums", __name__, url_prefix="/albums")
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
@login_required
|
||||
def index():
|
||||
db = get_db()
|
||||
albums = db.execute(
|
||||
"""
|
||||
SELECT album.id, name, uuid FROM album
|
||||
JOIN contient_user ON album_id = album.id
|
||||
JOIN user ON user_id = user.id
|
||||
WHERE user.id = ?
|
||||
""",
|
||||
(session.get("user_id"),),
|
||||
).fetchall()
|
||||
|
||||
return render_template("albums/index.html", albums=albums)
|
||||
|
||||
|
||||
@bp.route("/<uuid>")
|
||||
def album(uuid):
|
||||
"""
|
||||
Album page
|
||||
"""
|
||||
db = get_db()
|
||||
album = db.execute(
|
||||
"""
|
||||
SELECT id, name, uuid FROM album
|
||||
WHERE uuid = ?
|
||||
""",
|
||||
(uuid,),
|
||||
).fetchone()
|
||||
|
||||
if album is None:
|
||||
return abort(404)
|
||||
|
||||
partitions = db.execute(
|
||||
"""
|
||||
SELECT partition.uuid, partition.name, partition.author FROM partition
|
||||
JOIN contient_partition ON partition_uuid = partition.uuid
|
||||
JOIN album ON album.id = album_id
|
||||
WHERE album.uuid = ?
|
||||
""",
|
||||
(uuid,),
|
||||
).fetchall()
|
||||
|
||||
return render_template("albums/album.html", album=album, partitions=partitions)
|
||||
|
||||
|
||||
@bp.route("/<album_uuid>/<partition_uuid>")
|
||||
def partition(album_uuid, partition_uuid):
|
||||
"""
|
||||
Returns a partition in a given album
|
||||
"""
|
||||
db = get_db()
|
||||
partition = db.execute(
|
||||
"""
|
||||
SELECT * FROM partition
|
||||
JOIN contient_partition ON partition_uuid = partition.uuid
|
||||
JOIN album ON album.id = album_id
|
||||
WHERE album.uuid = ?
|
||||
AND partition.uuid = ?
|
||||
""",
|
||||
(album_uuid, partition_uuid),
|
||||
).fetchone()
|
||||
|
||||
if partition is None:
|
||||
return abort(404)
|
||||
|
||||
return send_file(os.path.join("partitions", f"{partition_uuid}.pdf"))
|
||||
|
||||
|
||||
@bp.route("/create-album", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def create_album():
|
||||
if request.method == "POST":
|
||||
name = request.form["name"]
|
||||
db = get_db()
|
||||
error = None
|
||||
|
||||
if not name:
|
||||
error = "Un nom est requis."
|
||||
|
||||
if error is None:
|
||||
while True:
|
||||
try:
|
||||
uuid = str(uuid4())
|
||||
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO album (uuid, name)
|
||||
VALUES (?, ?)
|
||||
""",
|
||||
(uuid, name),
|
||||
)
|
||||
db.commit()
|
||||
|
||||
album_id = db.execute(
|
||||
"""
|
||||
SELECT id FROM album
|
||||
WHERE uuid = ?
|
||||
""",
|
||||
(uuid,),
|
||||
).fetchone()["id"]
|
||||
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO contient_user (user_id, album_id)
|
||||
VALUES (?, ?)
|
||||
""",
|
||||
(session.get("user_id"), album_id),
|
||||
)
|
||||
db.commit()
|
||||
|
||||
break
|
||||
except db.IntegrityError:
|
||||
pass
|
||||
|
||||
return redirect(f"/albums/{uuid}")
|
||||
|
||||
flash(error)
|
||||
return render_template("albums/create-album.html")
|
||||
|
||||
return render_template("albums/create-album.html")
|
120
partitioncloud/auth.py
Normal file
120
partitioncloud/auth.py
Normal file
@ -0,0 +1,120 @@
|
||||
#!/usr/bin/python3
|
||||
"""
|
||||
Authentification module
|
||||
"""
|
||||
import functools
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
flash,
|
||||
g,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
|
||||
from .db import get_db
|
||||
|
||||
bp = Blueprint("auth", __name__, url_prefix="/auth")
|
||||
|
||||
|
||||
def login_required(view):
|
||||
"""View decorator that redirects anonymous users to the login page."""
|
||||
|
||||
@functools.wraps(view)
|
||||
def wrapped_view(**kwargs):
|
||||
if g.user is None:
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
return view(**kwargs)
|
||||
|
||||
return wrapped_view
|
||||
|
||||
|
||||
@bp.before_app_request
|
||||
def load_logged_in_user():
|
||||
"""If a user id is stored in the session, load the user object from
|
||||
the database into ``g.user``."""
|
||||
user_id = session.get("user_id")
|
||||
|
||||
if user_id is None:
|
||||
g.user = None
|
||||
else:
|
||||
g.user = (
|
||||
get_db().execute("SELECT * FROM user WHERE id = ?", (user_id,)).fetchone()
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/register", methods=("GET", "POST"))
|
||||
def register():
|
||||
"""Register a new user.
|
||||
Validates that the username is not already taken. Hashes the
|
||||
password for security.
|
||||
"""
|
||||
if request.method == "POST":
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
db = get_db()
|
||||
error = None
|
||||
|
||||
if not username:
|
||||
error = "Un nom d'utilisateur est requis."
|
||||
elif not password:
|
||||
error = "Un mot de passe est requis."
|
||||
|
||||
if error is None:
|
||||
try:
|
||||
db.execute(
|
||||
"INSERT INTO user (username, password) VALUES (?, ?)",
|
||||
(username, generate_password_hash(password)),
|
||||
)
|
||||
db.commit()
|
||||
except db.IntegrityError:
|
||||
# The username was already taken, which caused the
|
||||
# commit to fail. Show a validation error.
|
||||
error = f"Le nom d'utilisateur {username} est déjà pris."
|
||||
else:
|
||||
# Success, go to the login page.
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
flash(error)
|
||||
|
||||
return render_template("auth/register.html")
|
||||
|
||||
|
||||
@bp.route("/login", methods=("GET", "POST"))
|
||||
def login():
|
||||
"""Log in a registered user by adding the user id to the session."""
|
||||
if request.method == "POST":
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
db = get_db()
|
||||
error = None
|
||||
user = db.execute(
|
||||
"SELECT * FROM user WHERE username = ?", (username,)
|
||||
).fetchone()
|
||||
|
||||
if user is None:
|
||||
error = "Incorrect username."
|
||||
elif not check_password_hash(user["password"], password):
|
||||
error = "Incorrect password."
|
||||
|
||||
if error is None:
|
||||
# store the user id in a new session and return to the index
|
||||
session.clear()
|
||||
session["user_id"] = user["id"]
|
||||
return redirect(url_for("albums.index"))
|
||||
|
||||
flash(error)
|
||||
|
||||
return render_template("auth/login.html")
|
||||
|
||||
|
||||
@bp.route("/logout")
|
||||
def logout():
|
||||
"""Clear the current session, including the stored user id."""
|
||||
session.clear()
|
||||
return redirect(url_for("auth.login"))
|
28
partitioncloud/db.py
Normal file
28
partitioncloud/db.py
Normal file
@ -0,0 +1,28 @@
|
||||
import sqlite3
|
||||
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
|
||||
|
||||
def get_db():
|
||||
"""Connect to the application's configured database. The connection
|
||||
is unique for each request and will be reused if this is called
|
||||
again.
|
||||
"""
|
||||
if "db" not in g:
|
||||
g.db = sqlite3.connect(
|
||||
current_app.config["DATABASE"], detect_types=sqlite3.PARSE_DECLTYPES
|
||||
)
|
||||
g.db.row_factory = sqlite3.Row
|
||||
|
||||
return g.db
|
||||
|
||||
|
||||
def close_db(e=None):
|
||||
"""If this request connected to the database, close the
|
||||
connection.
|
||||
"""
|
||||
db = g.pop("db", None)
|
||||
|
||||
if db is not None:
|
||||
db.close()
|
@ -12,21 +12,22 @@ CREATE TABLE user (
|
||||
);
|
||||
|
||||
CREATE TABLE partition (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
uuid TEXT(36) PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
author TEXT,
|
||||
body TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE album (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
uuid TEXT(36) UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE contient_partition (
|
||||
partition_id INTEGER NOT NULL,
|
||||
partition_uuid TEXT(36) NOT NULL,
|
||||
album_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (partition_id, album_id)
|
||||
PRIMARY KEY (partition_uuid, album_id)
|
||||
);
|
||||
|
||||
CREATE TABLE contient_user (
|
||||
|
19
partitioncloud/templates/albums/album.html
Normal file
19
partitioncloud/templates/albums/album.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}{{ album["name"] }}{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if partitions|length != 0 %}
|
||||
{% for partition in partitions %}
|
||||
<a href="{{ album['uuid'] }}/{{ partition['uuid'] }}">
|
||||
<div class="partition-cover" id="partition-{{ partition['uuid'] }}">
|
||||
{{ partition["name"] }}
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div>Aucune partition disponible</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
13
partitioncloud/templates/albums/create-album.html
Normal file
13
partitioncloud/templates/albums/create-album.html
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}Nouvel Album{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
<label for="name">Nom</label>
|
||||
<input name="name" id="name" required>
|
||||
<input type="submit" value="Créer">
|
||||
</form>
|
||||
{% endblock %}
|
22
partitioncloud/templates/albums/index.html
Normal file
22
partitioncloud/templates/albums/index.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}Albums{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if albums|length != 0 %}
|
||||
{% for album in albums %}
|
||||
<a href="{{ album['uuid'] }}">
|
||||
<div class="album-cover" id="album-{{ album['id'] }}">
|
||||
{{ album["name"] }}
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div>Aucun album disponible</div>
|
||||
{% endif %}
|
||||
<a href="create-album">
|
||||
<button id="new-album">Nouvel Album</button>
|
||||
</a>
|
||||
{% endblock %}
|
15
partitioncloud/templates/auth/login.html
Normal file
15
partitioncloud/templates/auth/login.html
Normal file
@ -0,0 +1,15 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}Se connecter{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
<label for="username">Nom d'utilisateur</label>
|
||||
<input name="username" id="username" required>
|
||||
<label for="password">Mot de passe</label>
|
||||
<input type="password" name="password" id="password" required>
|
||||
<input type="submit" value="Se connecter">
|
||||
</form>
|
||||
{% endblock %}
|
15
partitioncloud/templates/auth/register.html
Normal file
15
partitioncloud/templates/auth/register.html
Normal file
@ -0,0 +1,15 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}Créer un compte{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
<label for="username">Nom d'utilisateur</label>
|
||||
<input name="username" id="username" required>
|
||||
<label for="password">Mot de passe</label>
|
||||
<input type="password" name="password" id="password" required>
|
||||
<input type="submit" value="Créer un compte">
|
||||
</form>
|
||||
{% endblock %}
|
24
partitioncloud/templates/base.html
Normal file
24
partitioncloud/templates/base.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!doctype html>
|
||||
<title>{% block title %}{% endblock %} - PartitionCloud</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
<nav>
|
||||
<h1><a href="{{ url_for('albums.index') }}">PartitionCloud</a></h1>
|
||||
<ul>
|
||||
{% if g.user %}
|
||||
<li><span>{{ g.user['username'] }}</span>
|
||||
<li><a href="{{ url_for('auth.logout') }}">Déconnexion</a>
|
||||
{% else %}
|
||||
<li><a href="{{ url_for('auth.register') }}">Créer un compte</a>
|
||||
<li><a href="{{ url_for('auth.login') }}">Se connecter</a>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
<section class="content">
|
||||
<header>
|
||||
{% block header %}{% endblock %}
|
||||
</header>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="flash">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% block content %}{% endblock %}
|
||||
</section>
|
Loading…
Reference in New Issue
Block a user