Reduced albums & groupes UUID's size

Added a new "share" dialog
This commit is contained in:
augustin64 2023-11-19 18:45:25 +01:00
parent 8ef2928ab3
commit f697bd0498
12 changed files with 261 additions and 57 deletions

View File

@ -13,3 +13,6 @@ MAX_ONLINE_QUERIES=3
# Disable registration of new users via /auth/register (they can still be added by root) # Disable registration of new users via /auth/register (they can still be added by root)
DISABLE_REGISTER=False DISABLE_REGISTER=False
# Front URL of the application (for QRCodes generation)
BASE_URL="http://localhost:5000"

View File

@ -4,14 +4,13 @@ Albums module
""" """
import os import os
import shutil import shutil
from uuid import uuid4
from flask import (Blueprint, abort, flash, redirect, render_template, request, from flask import (Blueprint, abort, flash, redirect, render_template, request,
send_file, session, current_app) send_file, session, current_app)
from .auth import login_required from .auth import login_required
from .db import get_db from .db import get_db
from .utils import User, Album, get_all_partitions from .utils import User, Album, get_all_partitions, new_uuid, get_qrcode, format_uuid
from . import search from . import search
bp = Blueprint("albums", __name__, url_prefix="/albums") bp = Blueprint("albums", __name__, url_prefix="/albums")
@ -71,25 +70,34 @@ def album(uuid):
""" """
try: try:
album = Album(uuid=uuid) album = Album(uuid=uuid)
album.users = [User(user_id=i["id"]) for i in album.get_users()]
user = User(user_id=session.get("user_id"))
partitions = album.get_partitions()
if user.id is None:
# On ne propose pas aux gens non connectés de rejoindre l'album
not_participant = False
else:
not_participant = not user.is_participant(album.uuid)
return render_template(
"albums/album.html",
album=album,
partitions=partitions,
not_participant=not_participant,
user=user
)
except LookupError: except LookupError:
return abort(404) try:
album = Album(uuid=format_uuid(uuid))
return redirect(f"/albums/{format_uuid(uuid)}")
except LookupError:
return abort(404)
album.users = [User(user_id=i["id"]) for i in album.get_users()]
user = User(user_id=session.get("user_id"))
partitions = album.get_partitions()
if user.id is None:
# On ne propose pas aux gens non connectés de rejoindre l'album
not_participant = False
else:
not_participant = not user.is_participant(album.uuid)
return render_template(
"albums/album.html",
album=album,
partitions=partitions,
not_participant=not_participant,
user=user
)
@bp.route("/<uuid>/qr")
def qr_code(uuid):
return get_qrcode(f"/albums/{uuid}")
@bp.route("/create-album", methods=["POST"]) @bp.route("/create-album", methods=["POST"])
@ -107,7 +115,7 @@ def create_album():
if error is None: if error is None:
while True: while True:
try: try:
uuid = str(uuid4()) uuid = new_uuid()
db.execute( db.execute(
""" """

View File

@ -3,14 +3,13 @@
Groupe module Groupe module
""" """
import os import os
from uuid import uuid4
from flask import (Blueprint, abort, flash, redirect, render_template, request, from flask import (Blueprint, abort, flash, redirect, render_template, request,
send_file, session, current_app) send_file, session, current_app)
from .auth import login_required from .auth import login_required
from .db import get_db from .db import get_db
from .utils import User, Album, get_all_partitions, Groupe from .utils import User, Album, get_all_partitions, Groupe, new_uuid, get_qrcode, format_uuid
from . import search from . import search
bp = Blueprint("groupe", __name__, url_prefix="/groupe") bp = Blueprint("groupe", __name__, url_prefix="/groupe")
@ -28,25 +27,34 @@ def groupe(uuid):
""" """
try: try:
groupe = Groupe(uuid=uuid) groupe = Groupe(uuid=uuid)
groupe.users = [User(user_id=i["id"]) for i in groupe.get_users()]
groupe.get_albums()
user = User(user_id=session.get("user_id"))
if user.id is None:
# On ne propose pas aux gens non connectés de rejoindre l'album
not_participant = False
else:
not_participant = not user.id in [i.id for i in groupe.users]
return render_template(
"groupe/index.html",
groupe=groupe,
not_participant=not_participant,
user=user
)
except LookupError: except LookupError:
return abort(404) try:
groupe = Groupe(uuid=format_uuid(uuid))
return redirect(f"/groupe/{format_uuid(uuid)}")
except LookupError:
return abort(404)
groupe.users = [User(user_id=i["id"]) for i in groupe.get_users()]
groupe.get_albums()
user = User(user_id=session.get("user_id"))
if user.id is None:
# On ne propose pas aux gens non connectés de rejoindre l'album
not_participant = False
else:
not_participant = not user.id in [i.id for i in groupe.users]
return render_template(
"groupe/index.html",
groupe=groupe,
not_participant=not_participant,
user=user
)
@bp.route("/<uuid>/qr")
def album_qr_code(uuid):
return get_qrcode(f"/groupe/{uuid}")
@ -65,7 +73,7 @@ def create_groupe():
if error is None: if error is None:
while True: while True:
try: try:
uuid = str(uuid4()) uuid = new_uuid()
db.execute( db.execute(
""" """
@ -182,7 +190,7 @@ def create_album(groupe_uuid):
if error is None: if error is None:
while True: while True:
try: try:
uuid = str(uuid4()) uuid = new_uuid()
db.execute( db.execute(
""" """
@ -228,11 +236,19 @@ def album(groupe_uuid, album_uuid):
try: try:
groupe = Groupe(uuid=groupe_uuid) groupe = Groupe(uuid=groupe_uuid)
except LookupError: except LookupError:
abort(404) try:
groupe = Groupe(uuid=format_uuid(groupe_uuid))
return redirect(f"/groupe/{format_uuid(groupe_uuid)}/{album_uuid}")
except LookupError:
return abort(404)
album_list = [a for a in groupe.get_albums() if a.uuid == album_uuid] album_list = [a for a in groupe.get_albums() if a.uuid == album_uuid]
if len(album_list) == 0: if len(album_list) == 0:
abort(404) album_uuid = format_uuid(album_uuid)
album_list = [a for a in groupe.get_albums() if a.uuid == album_uuid]
if len(album_list) != 0:
return redirect(f"/groupe/{groupe_uuid}/{album_uuid}")
return abort(404)
album = album_list[0] album = album_list[0]
user = User(user_id=session.get("user_id")) user = User(user_id=session.get("user_id"))
@ -257,3 +273,8 @@ def album(groupe_uuid, album_uuid):
not_participant=not_participant, not_participant=not_participant,
user=user user=user
) )
@bp.route("/<groupe_uuid>/<album_uuid>/qr")
def groupe_qr_code(groupe_uuid, album_uuid):
return get_qrcode(f"/groupe/{groupe_uuid}/{album_uuid}")

View File

@ -1,7 +1,29 @@
#!/usr/bin/python3 #!/usr/bin/python3
import os import os
import io
import random
import string
import qrcode
from .db import get_db from .db import get_db
from flask import current_app, send_file
def new_uuid():
return ''.join([random.choice(string.ascii_uppercase + string.digits) for _ in range(6)])
def format_uuid(uuid):
"""Format old uuid4 format"""
return uuid.upper()[:6]
def get_qrcode(location):
complete_url = current_app.config["BASE_URL"] + location
img_io = io.BytesIO()
qrcode.make(complete_url).save(img_io)
img_io.seek(0)
return send_file(img_io, mimetype="image/jpeg")
from .classes.user import User from .classes.user import User
from .classes.album import Album from .classes.album import Album

View File

@ -28,7 +28,7 @@ CREATE TABLE partition (
CREATE TABLE album ( CREATE TABLE album (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
uuid TEXT(36) UNIQUE NOT NULL uuid TEXT(6) UNIQUE NOT NULL
); );
CREATE TABLE contient_partition ( CREATE TABLE contient_partition (
@ -52,7 +52,7 @@ CREATE TABLE search_results (
CREATE TABLE groupe ( CREATE TABLE groupe (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
uuid TEXT(36) NOT NULL uuid TEXT(6) UNIQUE NOT NULL
); );
CREATE TABLE groupe_contient_user ( CREATE TABLE groupe_contient_user (

View File

@ -750,3 +750,18 @@ midi-player {
justify-content: center; justify-content: center;
display: flex; display: flex;
} }
#share-qrcode {
margin: 20px;
margin-top: 50px;
border-radius: 15px;
}
#share-url {
background-color: var(--color-surface1);
width: fit-content;
border-radius: 4px;
padding: 10px;
margin-bottom: 100px;
margin-top: 20px;
}

View File

@ -24,6 +24,15 @@
</form> </form>
<a href="#!" class="close-dialog">Close</a> <a href="#!" class="close-dialog">Close</a>
</dialog> </dialog>
{% if groupe %}
{% set current_url = "/groupe/" + groupe.uuid + "/" + album.uuid %}
{% else %}
{% set current_url = "/albums/" + album.uuid %}
{% endif %}
{% with share_link=config.BASE_URL+current_url, share_qrlink=current_url + "/qr" %}
{% include 'components/share_dialog.html' %}
{% endwith %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -53,6 +62,7 @@
{% elif album.users | length > 1 %} {% elif album.users | length > 1 %}
<a href="/albums/{{ album.uuid }}/quit">Quitter</a> <a href="/albums/{{ album.uuid }}/quit">Quitter</a>
{% endif %} {% endif %}
<a href="#share">Partager</a>
{% if g.user.access_level == 1 or (not not_participant and album.users | length == 1) %} {% if g.user.access_level == 1 or (not not_participant and album.users | length == 1) %}
<a id="delete-album" href="#delete">Supprimer</a> <a id="delete-album" href="#delete">Supprimer</a>
{% endif %} {% endif %}

View File

@ -0,0 +1,10 @@
<dialog id="share">
<center>
<img src="{{ share_qrlink }}" id="share-qrcode"><br/>
<div id="share-url" onclick='navigator.clipboard.writeText("{{ share_link }}")'>
{{ share_link }}
</div>
</center>
<a href="#!" class="close-dialog">Close</a>
</dialog>

View File

@ -22,6 +22,10 @@
</form> </form>
<a href="#!" class="close-dialog">Close</a> <a href="#!" class="close-dialog">Close</a>
</dialog> </dialog>
{% set current_url = "/groupe/" + groupe.uuid %}
{% with share_link=config.BASE_URL+current_url, share_qrlink=current_url + "/qr" %}
{% include 'components/share_dialog.html' %}
{% endwith %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -44,6 +48,7 @@
{% elif groupe.users | length > 1 %} {% elif groupe.users | length > 1 %}
<a href="/groupe/{{ groupe.uuid }}/quit">Quitter</a> <a href="/groupe/{{ groupe.uuid }}/quit">Quitter</a>
{% endif %} {% endif %}
<a href="#share">Partager</a>
{% if g.user.access_level == 1 or user.id in groupe.get_admins() %} {% if g.user.access_level == 1 or user.id in groupe.get_admins() %}
<a href="#create-groupe-album">Ajouter un album</a> <a href="#create-groupe-album">Ajouter un album</a>
<a id="delete-album" href="#delete">Supprimer</a> <a id="delete-album" href="#delete">Supprimer</a>

View File

@ -1,9 +1,27 @@
import random
import string
import sqlite3 import sqlite3
def run_sqlite_command(cmd): def run_sqlite_command(*args):
"""Run a command against the database""" """Run a command against the database"""
con = sqlite3.connect("instance/partitioncloud.sqlite") con = sqlite3.connect("instance/partitioncloud.sqlite")
cur = con.cursor() cur = con.cursor()
cur.execute(cmd) cur.execute(*args)
con.commit() con.commit()
con.close() con.close()
def get_sqlite_data(*args):
"""Get data from the db"""
con = sqlite3.connect("instance/partitioncloud.sqlite")
cur = con.cursor()
data = cur.execute(*args)
new_data = [i for i in data]
con.close()
return new_data
def new_uuid():
return ''.join([random.choice(string.ascii_uppercase + string.digits) for _ in range(6)])
def format_uuid(uuid):
"""Format old uuid4 format"""
return uuid.upper()[:6]

View File

@ -1,6 +1,11 @@
import os import os
import sqlite3
from hooks import utils from hooks import utils
from colorama import Fore, Style
"""
v1.3.*
"""
def add_source(): def add_source():
utils.run_sqlite_command( utils.run_sqlite_command(
"ALTER TABLE partition ADD source TEXT DEFAULT 'unknown'" "ALTER TABLE partition ADD source TEXT DEFAULT 'unknown'"
@ -44,3 +49,85 @@ def add_attachments():
def install_colorama(): def install_colorama():
os.system("pip install colorama -qq") os.system("pip install colorama -qq")
"""
v1.4.*
"""
def mass_rename():
"""Rename all albums & groupes to use a shorter uuid"""
albums = utils.get_sqlite_data("SELECT * FROM album")
groupes = utils.get_sqlite_data("SELECT * FROM groupe")
utils.run_sqlite_command(
"ALTER TABLE groupe RENAME TO _groupe_old"
)
utils.run_sqlite_command(
"ALTER TABLE album RENAME TO _album_old"
)
utils.run_sqlite_command( # Add UNIQUE constraint & change uuid length
"""CREATE TABLE groupe (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
uuid TEXT(6) UNIQUE NOT NULL
);"""
)
utils.run_sqlite_command( # Change uuid length
"""CREATE TABLE album (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
uuid TEXT(6) UNIQUE NOT NULL
);"""
)
for album in albums:
try:
utils.run_sqlite_command(
"""
INSERT INTO album (id, name, uuid)
VALUES (?, ?, ?)
""",
(album[0], album[1], utils.format_uuid(album[2]))
)
except sqlite3.IntegrityError:
uuid = new_uuid()
print(f"{Fore.RED}Collision on {album[1]}{Style.RESET_ALL} ({album[2][:10]} renaming to {uuid})")
utils.run_sqlite_command(
"""
INSERT INTO album (id, name, uuid)
VALUES (?, ?, ?)
""",
(album[0], album[1], uuid)
)
for groupe in groupes:
try:
utils.run_sqlite_command(
"""
INSERT INTO groupe (id, name, uuid)
VALUES (?, ?, ?)
""",
(groupe[0], groupe[1], utils.format_uuid(groupe[2]))
)
except sqlite3.IntegrityError:
uuid = new_uuid()
print(f"{Fore.RED}Collision on {groupe[1]}{Style.RESET_ALL} ({groupe[2][:10]} renaming to {uuid})")
utils.run_sqlite_command(
"""
INSERT INTO groupe (id, name, uuid)
VALUES (?, ?, ?)
""",
(groupe[0], groupe[1], uuid)
)
utils.run_sqlite_command(
"DROP TABLE _groupe_old"
)
utils.run_sqlite_command(
"DROP TABLE _album_old"
)
def base_url_parameter_added():
print(f"{Style.BRIGHT}{Fore.YELLOW}The parameter BASE_URL has been added, reference your front url in it{Style.RESET_ALL}")

View File

@ -32,6 +32,10 @@ hooks = [
]), ]),
("v1.3.3", [ ("v1.3.3", [
("Install colorama", v1.install_colorama) ("Install colorama", v1.install_colorama)
]),
("v1.4.0", [
("Change all albums & groupes uuids", v1.mass_rename),
("Warn new parameter", v1.base_url_parameter_added)
]) ])
] ]
@ -60,7 +64,7 @@ def backup_instance(version, verbose=True):
if verbose: if verbose:
print(*args) print(*args)
print_verbose("Backing up current instance") print_verbose("\nBacking up current instance")
dest = os.path.join("backups", version) dest = os.path.join("backups", version)
if os.path.exists(dest): if os.path.exists(dest):
@ -95,7 +99,7 @@ def apply_hooks(hooks):
subhook[1]() subhook[1]()
def migrate(current, target, skip_backup=False): def migrate(current, target, skip_backup=False, prog_name="scripts/migration.py"):
"""""" """"""
print(f"Trying to migrate from {args.current} to {args.target}") print(f"Trying to migrate from {args.current} to {args.target}")
@ -115,7 +119,8 @@ def migrate(current, target, skip_backup=False):
if not skip_backup: if not skip_backup:
backup_instance(current) backup_instance(current)
print(f"Instance backed up in {Style.BRIGHT}backups/{current}{Style.RESET_ALL}") print(f"Instance backed up in {Style.BRIGHT}backups/{current}{Style.RESET_ALL}\n")
print(f"If something goes wrong, recover with {Style.BRIGHT}{Fore.BLUE}{prog_name} --restore {current}{Style.RESET_ALL}")
else: else:
print("Skipping automatic backup") print("Skipping automatic backup")