Add the ability to attach files to a partition

This commit is contained in:
augustin64 2023-10-26 14:14:40 +02:00
parent 971d54af41
commit f8670f4901
16 changed files with 337 additions and 30 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ partitioncloud/partitions
partitioncloud/search-partitions
partitioncloud/static/thumbnails
partitioncloud/static/search-thumbnails
partitioncloud/attachments

View File

@ -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"

View File

@ -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()

View File

@ -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}"

View File

@ -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()
).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]

View File

@ -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("/<uuid>")
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("/<uuid>/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("/<uuid>/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/<uuid>.<filetype>")
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("/<uuid>/edit", methods=["GET", "POST"])
@login_required
def edit(uuid):

View File

@ -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
]

View File

@ -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
);

View File

@ -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;
}

View File

@ -18,7 +18,12 @@
</div>
</div>
</a>
<a href="/partition/{{ partition['uuid'] }}/details"><div class="edit-button">🔍</div></a>
<div class="partition-buttons">
{% if partition["has_attachment"] %}
<a href="/partition/{{ partition['uuid'] }}/attachments"><div class="partition-action">📎</div></a>
{% endif %}
<a href="/partition/{{ partition['uuid'] }}/details"><div class="edit-button partition-action">🔍</div></a>
</div>
</div>
{% endfor %}
</div>

View File

@ -10,7 +10,7 @@
<input name="name" type="text" required="" placeholder="Titre"><br/>
<input name="author" type="text" placeholder="Auteur"><br/>
<textarea id="paroles" name="body" type="text" placeholder="Paroles"></textarea><br/>
<input name="file" type="file" required=""><br/>
<input name="file" type="file" accept=".pdf" required=""><br/>
<input type="submit" value="Ajouter">
</form>
<a href="#!" class="close-dialog">Close</a>
@ -75,9 +75,14 @@
</div>
</div>
</a>
{% if partition["user_id"] == g.user.id or g.user.access_level == 1 %}
<a href="/partition/{{ partition['uuid'] }}/edit"><div class="edit-button">✏️</div></a>
{% endif %}
<div class="partition-buttons">
{% if partition["has_attachment"] %}
<a href="/partition/{{ partition['uuid'] }}/attachments"><div class="partition-action">📎</div></a>
{% endif %}
{% if partition["user_id"] == g.user.id or g.user.access_level == 1 %}
<a href="/partition/{{ partition['uuid'] }}/edit"><div class="partition-action">✏️</div></a>
{% endif %}
</div>
</div>
{% endfor %}
</section>

View File

@ -17,6 +17,11 @@
</div>
</div>
</a>
<div class="partition-buttons">
{% if partition["has_attachment"] %}
<a href="/partition/{{ partition['uuid'] }}/attachments"><div class="partition-action">📎</div></a>
{% endif %}
</div>
<form action="/albums/add-partition" class="add-partition-form" method="post">
<select name="album-uuid">
{% for album in user.albums %}

View File

@ -170,9 +170,11 @@
</div>
</div>
<div id="content-container">
<header id="page-header">
<h1>PartitionCloud</h1>
</header>
{% if not DISABLE_HEADER %}
<header id="page-header">
<h1>PartitionCloud</h1>
</header>
{% endif %}
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}

View File

@ -0,0 +1,63 @@
{% set DISABLE_HEADER=true %}
{% extends 'base.html' %}
{% block title %}Attachments de {{ partition.name }}{% endblock %}
{% block dialogs %}
<dialog id="create-attachment">
<h2>Ajouter un attachment à {{ partition.name }}</h2>
<form action="/partition/{{ partition.uuid }}/add-attachment" method="post" enctype="multipart/form-data">
<input type="text" name="name" id="name" placeholder="Nom"><br/>
<input name="file" type="file" accept=".mp3,.mid" required=""><br/>
<input type="submit" value="Ajouter">
</form>
<a href="#!" class="close-dialog">Close</a>
</dialog>
{% endblock %}
{% block content %}
<object id="pdf-embed" width="400" height="500" type="application/pdf" data="/partition/{{ partition.uuid }}">
<p>
Impossible d'afficher le pdf dans ce navigateur.
Il est conseillé d'utiliser Firefox sur Android.
</p>
</object>
<script src="https://cdn.jsdelivr.net/combine/npm/tone@14.7.58,npm/@magenta/music@1.23.1/es6/core.js,npm/focus-visible@5,npm/html-midi-player@1.5.0"></script>
<midi-visualizer type="staff" id="midi-visualizer"></midi-visualizer>
{% if partition.attachments | length > 0 %}
<div id="attachments">
<table>
<tbody>
{% for attachment in partition.attachments %}
<tr>
{% if attachment.filetype == "mp3" %}
<td><audio controls src="/partition/attachment/{{ attachment.uuid }}.mp3"></td>
<td>🎙️ {{ attachment.name }}</td>
{% elif attachment.filetype == "mid" %}
<td><midi-player
src="/partition/attachment/{{ attachment.uuid }}.mid"
sound-font visualizer="#midi-visualizer" data-js-focus-visible>
</midi-player>
<noscript>MIDI support needs JavaScript</noscript>
</td>
<td>🎵 {{ attachment.name }}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<br/>
{% if user %}
<div class="centered">
<a href="#create-attachment"><button>Ajouter un attachment</button></a>
</div>
{% endif %}
{% endblock %}

View File

@ -64,6 +64,17 @@
<td>Paroles</td>
<td><textarea id="paroles" name="body" type="text" placeholder="Paroles">{{ partition.body }}</textarea><br/></td>
</tr>
<tr>
<td>Pièces jointes</td>
{% set _ = partition.load_attachments() %}
<td><a href="/partition/{{ partition.uuid }}/attachments">
{% if partition.attachments %}
Oui, {{ partition.attachments | length }}
{% else %}
En rajouter
{% endif %}
</a></td>
</tr>
</tbody>
</table>
<input type="submit" value="Mettre à jour" />

View File

@ -35,6 +35,17 @@
<td>Paroles</td>
<td><textarea id="paroles" name="body" type="text" placeholder="Paroles">{{ partition.body }}</textarea><br/></td>
</tr>
<tr>
<td>Pièces jointes</td>
{% set _ = partition.load_attachments() %}
<td><a href="/partition/{{ partition.uuid }}/attachments">
{% if partition.attachments %}
Oui, {{ partition.attachments | length }}
{% else %}
En rajouter
{% endif %}
</a></td>
</tr>
</tbody>
</table>
<input type="submit" value="Mettre à jour" />