From f8670f49010f32a4b6baddccde44a4288325e204 Mon Sep 17 00:00:00 2001 From: augustin64 Date: Thu, 26 Oct 2023 14:14:40 +0200 Subject: [PATCH] Add the ability to attach files to a partition --- .gitignore | 1 + make.sh | 1 + partitioncloud/modules/classes/album.py | 10 +- partitioncloud/modules/classes/attachment.py | 33 ++++++ partitioncloud/modules/classes/partition.py | 16 ++- partitioncloud/modules/partition.py | 112 ++++++++++++++++-- partitioncloud/modules/utils.py | 12 +- partitioncloud/schema.sql | 9 ++ partitioncloud/static/style.css | 55 ++++++++- .../templates/admin/partitions.html | 7 +- partitioncloud/templates/albums/album.html | 13 +- partitioncloud/templates/albums/search.html | 5 + partitioncloud/templates/base.html | 8 +- .../templates/partition/attachments.html | 63 ++++++++++ .../templates/partition/details.html | 11 ++ partitioncloud/templates/partition/edit.html | 11 ++ 16 files changed, 337 insertions(+), 30 deletions(-) create mode 100644 partitioncloud/modules/classes/attachment.py create mode 100644 partitioncloud/templates/partition/attachments.html diff --git a/.gitignore b/.gitignore index 1238215..e53c43c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ partitioncloud/partitions partitioncloud/search-partitions partitioncloud/static/thumbnails partitioncloud/static/search-thumbnails +partitioncloud/attachments diff --git a/make.sh b/make.sh index 322f1e2..967c26f 100755 --- a/make.sh +++ b/make.sh @@ -3,6 +3,7 @@ init () { mkdir -p "instance" mkdir -p "partitioncloud/partitions" + mkdir -p "partitioncloud/attachments" mkdir -p "partitioncloud/search-partitions" mkdir -p "partitioncloud/static/thumbnails" mkdir -p "partitioncloud/static/search-thumbnails" diff --git a/partitioncloud/modules/classes/album.py b/partitioncloud/modules/classes/album.py index 5d3c884..2b00231 100644 --- a/partitioncloud/modules/classes/album.py +++ b/partitioncloud/modules/classes/album.py @@ -65,10 +65,14 @@ class Album(): db = get_db() return db.execute( """ - SELECT partition.uuid, partition.name, partition.author, partition.user_id FROM partition - JOIN contient_partition ON partition_uuid = partition.uuid - JOIN album ON album.id = album_id + SELECT p.uuid, p.name, p.author, p.user_id, + CASE WHEN MAX(a.uuid) IS NOT NULL THEN 1 ELSE 0 END AS has_attachment + FROM partition AS p + JOIN contient_partition ON contient_partition.partition_uuid = p.uuid + JOIN album ON album.id = album_id + LEFT JOIN attachments AS a ON p.uuid = a.partition_uuid WHERE album.uuid = ? + GROUP BY p.uuid, p.name, p.author, p.user_id """, (self.uuid,), ).fetchall() diff --git a/partitioncloud/modules/classes/attachment.py b/partitioncloud/modules/classes/attachment.py new file mode 100644 index 0000000..f585abc --- /dev/null +++ b/partitioncloud/modules/classes/attachment.py @@ -0,0 +1,33 @@ +from flask import current_app + +from ..db import get_db + + +class Attachment(): + def __init__(self, uuid=None, data=None): + db = get_db() + if uuid is not None: + self.uuid = uuid + data = db.execute( + """ + SELECT * FROM attachments + WHERE uuid = ? + """, + (self.uuid,) + ).fetchone() + if data is None: + raise LookupError + + elif data is not None: + self.uuid = data["uuid"] + + else: + raise LookupError + + self.name = data["name"] + self.user_id = data["user_id"] + self.filetype = data["filetype"] + self.partition_uuid = data["partition_uuid"] + + def __repr__(self): + return f"{self.name}.{self.filetype}" \ No newline at end of file diff --git a/partitioncloud/modules/classes/partition.py b/partitioncloud/modules/classes/partition.py index 8128706..bef723e 100644 --- a/partitioncloud/modules/classes/partition.py +++ b/partitioncloud/modules/classes/partition.py @@ -3,6 +3,7 @@ from flask import current_app from ..db import get_db from .user import User +from .attachment import Attachment @@ -25,6 +26,7 @@ class Partition(): self.body = data["body"] self.user_id = data["user_id"] self.source = data["source"] + self.attachments = None else: raise LookupError @@ -94,4 +96,16 @@ class Partition(): WHERE partition_uuid = ? """, (self.uuid,), - ).fetchall() \ No newline at end of file + ).fetchall() + + def load_attachments(self): + db = get_db() + if self.attachments is None: + data = db.execute( + """ + SELECT * FROM attachments + WHERE partition_uuid = ? + """, + (self.uuid,) + ) + self.attachments = [Attachment(data=i) for i in data] diff --git a/partitioncloud/modules/partition.py b/partitioncloud/modules/partition.py index 1b76c0b..d5d70f4 100644 --- a/partitioncloud/modules/partition.py +++ b/partitioncloud/modules/partition.py @@ -3,11 +3,12 @@ Partition module """ import os +from uuid import uuid4 from flask import Blueprint, abort, send_file, render_template, request, redirect, flash, session from .db import get_db from .auth import login_required, admin_required -from .utils import get_all_partitions, User, Partition +from .utils import get_all_partitions, User, Partition, Attachment bp = Blueprint("partition", __name__, url_prefix="/partition") @@ -15,21 +16,110 @@ bp = Blueprint("partition", __name__, url_prefix="/partition") @bp.route("/") def partition(uuid): db = get_db() - partition = db.execute( - """ - SELECT * FROM partition - WHERE uuid = ? - """, - (uuid,) - ).fetchone() - - if partition is None: + try: + partition = Partition(uuid=uuid) + except LookupError: abort(404) + + return send_file( os.path.join("partitions", f"{uuid}.pdf"), - download_name = f"{partition['name']}.pdf" + download_name = f"{partition.name}.pdf" ) +@bp.route("//attachments") +def attachments(uuid): + db = get_db() + try: + partition = Partition(uuid=uuid) + except LookupError: + abort(404) + + partition.load_attachments() + return render_template( + "partition/attachments.html", + partition=partition, + user=User(user_id=session.get("user_id")) + ) + + +@bp.route("//add-attachment", methods=["POST"]) +@login_required +def add_attachment(uuid): + db = get_db() + try: + partition = Partition(uuid=uuid) + except LookupError: + abort(404) + user = User(user_id=session.get("user_id")) + + if user.id != partition.user_id and user.access_level != 1: + flash("Cette partition ne vous appartient pas") + return redirect(request.referrer) + + error = None # À mettre au propre + if "file" not in request.files: + error = "Aucun fichier n'a été fourni." + else: + if "name" not in request.form or request.form["name"] == "": + name = ".".join(request.files["file"].filename.split(".")[:-1]) + else: + name = request.form["name"] + + if name == "": + error = "Pas de nom de fichier" + + else: + filename = request.files["file"].filename + ext = filename.split(".")[-1] + if ext not in ["mid", "mp3"]: + error = "Extension de fichier non supportée" + + if error is not None: + flash(error) + return redirect(request.referrer) + + while True: + try: + attachment_uuid = str(uuid4()) + + db.execute( + """ + INSERT INTO attachments (uuid, name, filetype, partition_uuid, user_id) + VALUES (?, ?, ?, ?, ?) + """, + (attachment_uuid, name, ext, partition.uuid, user.id), + ) + db.commit() + + file = request.files["file"] + file.save(f"partitioncloud/attachments/{attachment_uuid}.{ext}") + break + + except db.IntegrityError: + pass + + + return redirect(f"/partition/{partition.uuid}/attachments") + + +@bp.route("/attachment/.") +def attachment(uuid, filetype): + db = get_db() + try: + attachment = Attachment(uuid=uuid) + except LookupError: + abort(404) + + assert filetype == attachment.filetype + + return send_file( + os.path.join("attachments", f"{uuid}.{attachment.filetype}"), + download_name = f"{attachment.name}.{attachment.filetype}" + ) + + + @bp.route("//edit", methods=["GET", "POST"]) @login_required def edit(uuid): diff --git a/partitioncloud/modules/utils.py b/partitioncloud/modules/utils.py index 31c8d5c..6239b72 100644 --- a/partitioncloud/modules/utils.py +++ b/partitioncloud/modules/utils.py @@ -7,13 +7,20 @@ from .classes.user import User from .classes.album import Album from .classes.groupe import Groupe from .classes.partition import Partition +from .classes.attachment import Attachment def get_all_partitions(): db = get_db() partitions = db.execute( """ - SELECT * FROM partition + SELECT p.uuid, p.name, p.author, p.body, p.user_id, + CASE WHEN MAX(a.uuid) IS NOT NULL THEN 1 ELSE 0 END AS has_attachment + FROM partition AS p + JOIN contient_partition ON contient_partition.partition_uuid = p.uuid + JOIN album ON album.id = album_id + LEFT JOIN attachments AS a ON p.uuid = a.partition_uuid + GROUP BY p.uuid, p.name, p.author, p.user_id """ ) # Transform sql object to dictionary usable in any thread @@ -23,7 +30,8 @@ def get_all_partitions(): "name": p["name"], "author": p["author"], "body": p["body"], - "user_id": p["user_id"] + "user_id": p["user_id"], + "has_attachment": p["has_attachment"] } for p in partitions ] diff --git a/partitioncloud/schema.sql b/partitioncloud/schema.sql index 12678bd..4ef3ccf 100644 --- a/partitioncloud/schema.sql +++ b/partitioncloud/schema.sql @@ -7,6 +7,7 @@ DROP TABLE IF EXISTS search_results; DROP TABLE IF EXISTS groupe; DROP TABLE IF EXISTS groupe_contient_user; DROP TABLE IF EXISTS groupe_contient_album; +DROP TABLE IF EXISTS attachments; CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -65,4 +66,12 @@ CREATE TABLE groupe_contient_album ( groupe_id INTEGER NOT NULL, album_id INTEGER NOT NULL, PRIMARY KEY (groupe_id, album_id) +); + +CREATE TABLE attachments ( + uuid TEXT(36) PRIMARY KEY, + name TEXT NOT NULL, + filetype TEXT NOT NULL DEFAULT 'mp3', + partition_uuid INTEGER NOT NULL, + user_id INTEGER NOT NULL ); \ No newline at end of file diff --git a/partitioncloud/static/style.css b/partitioncloud/static/style.css index 1b9a100..276a1c4 100644 --- a/partitioncloud/static/style.css +++ b/partitioncloud/static/style.css @@ -342,13 +342,18 @@ img.partition-thumbnail { min-height: 50px; } -.edit-button { - float: right; - transform: translateX(-96%) translateY(-162%); - padding: 2%; +.partition-action { + padding: 7px; + margin: 3px; border-radius: 3px; box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.2); - background-color: var(--color-blue); + background-color: #cdd6f4; +} + +.partition-buttons { + float: right; + display: flex; + transform: translateX(-22px) translateY(-115px); } @@ -704,4 +709,44 @@ td { .x-scrollable { overflow-x: auto; +} + + +/** Attachment page */ +#pdf-embed { + margin: auto; + + width: 100%; + width: -moz-available; + width: -webkit-fill-available; + width: stretch; + + height: 50vh; +} + +midi-visualizer { + background-color: white; + border-radius: 3px; +} + +midi-player { + color: black; +} + +#attachments { + overflow-y: scroll; +} + +#attachments > table { + border: none; +} + +#attachments > table > tbody > tr > td { + border: none; + min-width: fit-content; +} + +.centered { + justify-content: center; + display: flex; } \ No newline at end of file diff --git a/partitioncloud/templates/admin/partitions.html b/partitioncloud/templates/admin/partitions.html index 0645a08..c1170d4 100644 --- a/partitioncloud/templates/admin/partitions.html +++ b/partitioncloud/templates/admin/partitions.html @@ -18,7 +18,12 @@ -
🔍
+
+ {% if partition["has_attachment"] %} +
📎
+ {% endif %} +
🔍
+
{% endfor %} diff --git a/partitioncloud/templates/albums/album.html b/partitioncloud/templates/albums/album.html index dd227ef..7d3d15d 100644 --- a/partitioncloud/templates/albums/album.html +++ b/partitioncloud/templates/albums/album.html @@ -10,7 +10,7 @@


-
+
Close @@ -75,9 +75,14 @@ - {% if partition["user_id"] == g.user.id or g.user.access_level == 1 %} -
✏️
- {% endif %} +
+ {% if partition["has_attachment"] %} +
📎
+ {% endif %} + {% if partition["user_id"] == g.user.id or g.user.access_level == 1 %} +
✏️
+ {% endif %} +
{% endfor %} diff --git a/partitioncloud/templates/albums/search.html b/partitioncloud/templates/albums/search.html index 9cc5b63..50f1922 100644 --- a/partitioncloud/templates/albums/search.html +++ b/partitioncloud/templates/albums/search.html @@ -17,6 +17,11 @@ +
+ {% if partition["has_attachment"] %} +
📎
+ {% endif %} +

+
+ +
+ Close + +{% endblock %} + +{% block content %} + +

+ Impossible d'afficher le pdf dans ce navigateur. + Il est conseillé d'utiliser Firefox sur Android. +

+
+ + + + + +{% if partition.attachments | length > 0 %} +
+ + + {% for attachment in partition.attachments %} + + {% if attachment.filetype == "mp3" %} + + + {% elif attachment.filetype == "mid" %} + + + + {% endif %} + + {% endfor %} + +
🎙️ {{ attachment.name }} + + + 🎵 {{ attachment.name }}
+
+{% endif %} + +
+{% if user %} + +{% endif %} +{% endblock %} diff --git a/partitioncloud/templates/partition/details.html b/partitioncloud/templates/partition/details.html index a38669c..92569d9 100644 --- a/partitioncloud/templates/partition/details.html +++ b/partitioncloud/templates/partition/details.html @@ -64,6 +64,17 @@ Paroles
+ + Pièces jointes + {% set _ = partition.load_attachments() %} + + {% if partition.attachments %} + Oui, {{ partition.attachments | length }} + {% else %} + En rajouter + {% endif %} + + diff --git a/partitioncloud/templates/partition/edit.html b/partitioncloud/templates/partition/edit.html index e822cc3..29eb568 100644 --- a/partitioncloud/templates/partition/edit.html +++ b/partitioncloud/templates/partition/edit.html @@ -35,6 +35,17 @@ Paroles
+ + Pièces jointes + {% set _ = partition.load_attachments() %} + + {% if partition.attachments %} + Oui, {{ partition.attachments | length }} + {% else %} + En rajouter + {% endif %} + +