Compare commits

..

2 Commits

Author SHA1 Message Date
marc
23337f8ab3 Afegir cartells a les jams (i esquelet per a slow jams i notes) 2025-11-01 12:47:04 +01:00
marc
fbfedf4dac Fix set editor 2025-10-30 22:03:01 +01:00
28 changed files with 1657 additions and 213 deletions

View File

@@ -1,10 +1,12 @@
from folkugat_web.api.router import get_router
from . import index, live, set_page
from . import cartell, index, live, playlist, set_page
router = get_router()
router.include_router(cartell.router)
router.include_router(index.router)
router.include_router(live.router)
router.include_router(set_page.router)
router.include_router(playlist.router)
__all__ = ["router"]

View File

@@ -0,0 +1,71 @@
import dataclasses
from typing import Annotated
from fastapi import HTTPException, Request, UploadFile
from fastapi.params import File
from folkugat_web.api.router import get_router
from folkugat_web.fragments.sessio import cartell
from folkugat_web.services import auth
from folkugat_web.services import files as files_service
from folkugat_web.services import sessions as sessions_service
router = get_router()
@router.get("/api/sessio/{session_id}/cartell")
def get_cartell(
request: Request,
logged_in: auth.LoggedIn,
session_id: int,
):
return cartell.cartell(request, session_id, logged_in)
@router.get("/api/sessio/{session_id}/cartell/editor")
def get_cartell_editor(
request: Request,
_: auth.RequireLogin,
session_id: int,
):
return cartell.cartell_editor(request, session_id)
@router.put("/api/sessio/{session_id}/cartell")
async def set_cartell(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
upload_file: Annotated[UploadFile, File()],
):
session = sessions_service.get_session(session_id=session_id)
if not session:
raise HTTPException(status_code=404, detail="Could not find session")
url = await files_service.store_session_cartell(
session_id=session_id,
upload_file=upload_file,
)
new_session = dataclasses.replace(
session,
cartell_url=url,
)
sessions_service.set_session(new_session)
return cartell.cartell(request, session_id, logged_in)
@router.delete("/api/sessio/{session_id}/cartell")
async def delete_cartell(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
):
session = sessions_service.get_session(session_id=session_id)
if not session:
raise HTTPException(status_code=404, detail="Could not find session")
if session.cartell_url:
new_session = dataclasses.replace(
session,
cartell_url=None,
)
sessions_service.set_session(new_session)
files_service.clean_orphan_files()
return cartell.cartell(request, session_id, logged_in)

View File

@@ -2,7 +2,8 @@ from typing import Annotated
from fastapi import Form, Request
from folkugat_web.api.router import get_router
from folkugat_web.fragments import live, sessio
from folkugat_web.fragments import live
from folkugat_web.fragments.sessio import page as sessio
from folkugat_web.services import auth
from folkugat_web.services.temes import write as temes_service
from folkugat_web.templates import templates
@@ -52,150 +53,3 @@ def stop_live(
session_id: int,
):
return live.stop_live_session(request=request, session_id=session_id)
@router.post("/api/sessio/{session_id}/set")
def add_set(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
):
return sessio.add_set(request=request, session_id=session_id, logged_in=logged_in)
@router.get("/api/sessio/{session_id}/set/{set_id}")
def get_set(
request: Request,
logged_in: auth.LoggedIn,
session_id: int,
set_id: int,
):
return sessio.get_set(request=request, session_id=session_id, set_id=set_id, logged_in=logged_in)
@router.delete("/api/sessio/{session_id}/set/{set_id}")
def delete_set(
_: auth.RequireLogin,
session_id: int,
set_id: int,
):
return sessio.delete_set(session_id=session_id, set_id=set_id)
@router.post("/api/sessio/{session_id}/set/{set_id}")
def add_tema(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
set_id: int,
):
return sessio.add_tema(request=request, session_id=session_id, set_id=set_id, logged_in=logged_in)
@router.get("/api/sessio/{session_id}/set/{set_id}/tema/{entry_id}")
def get_tema(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
):
return sessio.get_tema(
request=request, session_id=session_id, set_id=set_id, entry_id=entry_id, logged_in=logged_in)
@router.get("/api/sessio/{session_id}/set/{set_id}/tema/{entry_id}/editor")
def get_tema_editor(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
):
return sessio.get_tema_editor(
request=request, session_id=session_id, set_id=set_id, entry_id=entry_id, logged_in=logged_in)
@router.delete("/api/sessio/{session_id}/set/{set_id}/tema/{entry_id}")
def delete_tema(
_: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
):
return sessio.delete_tema(session_id=session_id, set_id=set_id, entry_id=entry_id)
@router.get("/api/sessio/{session_id}/set/{set_id}/tema/{entry_id}/busca")
def busca_tema(
request: Request,
_: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
query: str,
):
return sessio.busca_tema(
request=request,
session_id=session_id,
set_id=set_id,
entry_id=entry_id,
query=query,
)
@router.put("/api/sessio/{session_id}/set/{set_id}/tema/{entry_id}")
def set_tema(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
tema_id: Annotated[int, Form()],
):
return sessio.set_tema(
request=request,
logged_in=logged_in,
session_id=session_id,
set_id=set_id,
entry_id=entry_id,
tema_id=tema_id,
)
@router.put("/api/sessio/{session_id}/set/{set_id}/tema/{entry_id}/unknown")
def set_tema_unknown(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
):
return sessio.set_tema(
request=request,
logged_in=logged_in,
session_id=session_id,
set_id=set_id,
entry_id=entry_id,
tema_id=None,
)
@router.post("/api/sessio/{session_id}/set/{set_id}/tema/{entry_id}")
def set_tema_new(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
title: Annotated[str, Form()],
):
new_tema = temes_service.create_tema(title=title)
return sessio.set_tema(
request=request,
logged_in=logged_in,
session_id=session_id,
set_id=set_id,
entry_id=entry_id,
tema_id=new_tema.id,
)

View File

@@ -0,0 +1,189 @@
from typing import Annotated
from fastapi import Form, Request
from folkugat_web.api.router import get_router
from folkugat_web.fragments import live
from folkugat_web.fragments.sessio import playlist
from folkugat_web.services import auth
from folkugat_web.services.temes import write as temes_service
from folkugat_web.templates import templates
router = get_router()
@router.post("/api/sessio/{session_id}/playlist/set")
def add_set(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
):
return playlist.add_set(
request=request,
session_id=session_id,
logged_in=logged_in,
)
@router.get("/api/sessio/{session_id}/playlist/set/{set_id}")
def get_set(
request: Request,
logged_in: auth.LoggedIn,
session_id: int,
set_id: int,
):
return playlist.get_set(
request=request,
session_id=session_id,
set_id=set_id,
logged_in=logged_in,
)
@router.delete("/api/sessio/{session_id}/playlist/set/{set_id}")
def delete_set(
_: auth.RequireLogin,
session_id: int,
set_id: int,
):
return playlist.delete_set(
session_id=session_id,
set_id=set_id,
)
@router.post("/api/sessio/{session_id}/playlist/set/{set_id}")
def add_tema(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
set_id: int,
):
return playlist.add_tema(
request=request,
session_id=session_id,
set_id=set_id,
logged_in=logged_in,
)
@router.get("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}")
def get_tema(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
):
return playlist.get_tema(
request=request,
session_id=session_id,
set_id=set_id,
entry_id=entry_id,
logged_in=logged_in,
)
@router.get("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}/editor")
def get_tema_editor(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
):
return playlist.get_tema_editor(
request=request,
session_id=session_id,
set_id=set_id,
entry_id=entry_id,
logged_in=logged_in,
)
@router.delete("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}")
def delete_tema(
_: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
):
return playlist.delete_tema(
session_id=session_id,
set_id=set_id,
entry_id=entry_id,
)
@router.get("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}/busca")
def busca_tema(
request: Request,
_: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
query: str,
):
return playlist.busca_tema(
request=request,
session_id=session_id,
set_id=set_id,
entry_id=entry_id,
query=query,
)
@router.put("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}")
def set_tema(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
tema_id: Annotated[int, Form()],
):
return playlist.set_tema(
request=request,
logged_in=logged_in,
session_id=session_id,
set_id=set_id,
entry_id=entry_id,
tema_id=tema_id,
)
@router.put("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}/unknown")
def set_tema_unknown(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
):
return playlist.set_tema(
request=request,
logged_in=logged_in,
session_id=session_id,
set_id=set_id,
entry_id=entry_id,
tema_id=None,
)
@router.post("/api/sessio/{session_id}/playlist/set/{set_id}/tema/{entry_id}")
def set_tema_new(
request: Request,
logged_in: auth.RequireLogin,
session_id: int,
set_id: int,
entry_id: int,
title: Annotated[str, Form()],
):
new_tema = temes_service.create_tema(title=title)
return playlist.set_tema(
request=request,
logged_in=logged_in,
session_id=session_id,
set_id=set_id,
entry_id=entry_id,
tema_id=new_tema.id,
)

View File

@@ -34,7 +34,7 @@ async def set_link(
upload_file: Annotated[UploadFile | None, File()] = None,
):
if upload_file:
url = await files_service.store_file(tema_id=tema_id, upload_file=upload_file)
url = await files_service.store_tema_file(tema_id=tema_id, upload_file=upload_file)
link_type = links_service.guess_link_type(url or '')
new_link = model.Link(
@@ -65,7 +65,7 @@ def create_link(
@router.delete("/api/tema/{tema_id}/link/{link_id}")
def delete_link(
_logged_in: auth.RequireLogin,
_: auth.RequireLogin,
tema_id: int,
link_id: int,
):

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,36 @@
<div id="cartell"
class="flex flex-col items-center">
{% if session.cartell_url %}
<div class="max-w-[655px] w-full
flex flex-col items-center">
{% if logged_in %}
<div class="my-2 w-full text-beige text-right">
<button title="Modifica el cartell"
class="mx-1"
hx-get="/api/sessio/{{ session_id }}/cartell/editor"
hx-target="#cartell"
hx-swap="outerHTML">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
<button title="Esborra el cartell"
class="mx-1"
hx-delete="/api/sessio/{{ session_id }}/cartell"
hx-target="#cartell"
hx-swap="outerHTML">
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</div>
{% endif %}
<img class="w-full h-auto"
src="{{ session.cartell_url }}"/>
</div>
{% elif logged_in %}
<button class="text-beige mt-2"
hx-get="/api/sessio/{{ session_id }}/cartell/editor"
hx-target="#cartell"
hx-swap="outerHTML">
<i class="fa fa-plus" aria-hidden="true"></i>
Afegeix cartell
</button>
{% endif %}
</div>

View File

@@ -0,0 +1,23 @@
<form id="cartell-editor"
class="flex flex-row gap-2"
hx-encoding="multipart/form-data">
<input type='file'
class="border border-beige focus:outline-none
rounded grow
bg-brown p-1 my-1"
name='upload_file'/>
<button title="Desa els canvis"
class="mx-1 text-beige"
hx-put="/api/sessio/{{ session_id }}/cartell"
hx-target="#cartell-editor"
hx-swap="outerHTML">
<i class="fa fa-check" aria-hidden="true"></i>
</button>
<button title="Descarta els canvis"
class="mx-1 text-beige"
hx-get="/api/sessio/{{ session_id }}/cartell"
hx-target="#cartell-editor"
hx-swap="outerHTML">
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</form>

View File

@@ -12,6 +12,7 @@
{% endif %}
{% endif %}
</h3>
{% include "fragments/sessio/cartell.html" %}
<div class="text-left">
<h4 class="pt-4 text-xl text-beige">Horari i lloc</h4>
De {{ session.start_time.strftime("%H:%M") }}
@@ -29,6 +30,17 @@
{% endif %}
{% endif %}
</div>
{% if logged_in or session.notes %}
<div class="text-left">
<h4 class="py-4 text-xl text-beige">Notes</h4>
{% if session.notes %}{{ session.notes }}{% endif %}
</div>
{% endif %}
{% if logged_in or False %}
<div class="text-left">
<h4 class="py-4 text-xl text-beige">Slow Jam</h4>
</div>
{% endif %}
{% if logged_in or playlist.sets %}
<div class="text-left">
<h4 class="py-4 text-xl text-beige mt-2">Temes tocats</h4>

View File

@@ -7,7 +7,7 @@
{% if logged_in %}
<div class="flex flex-col items-center">
<button class="text-beige mt-2"
hx-post="/api/sessio/{{ session.id }}/set"
hx-post="/api/sessio/{{ session.id }}/playlist/set"
hx-target="#playlist-{{ session.id }}"
hx-swap="beforeend transition:true">
<i class="fa fa-plus" aria-hidden="true"></i>

View File

@@ -2,18 +2,22 @@
m-4 rounded-lg bg-white
px-2 py-1 my-1"
id="set-entry-{{ set_id }}"
hx-get="/api/sessio/{{ session_id }}/set/{{ set_id }}"
hx-get="/api/sessio/{{ session_id }}/playlist/set/{{ set_id }}"
hx-target="#set-entry-{{ set_id }}"
hx-swap="outerHTML"
hx-trigger="reload-set-{{ set_id }}">
{% if set_entry.temes | length > 1 or not set_entry.temes[0] %}
{% if set_entry.temes | length > 1 or not set_entry.temes[0].tema %}
{% set set_url = "/sessio/%d/set/%d" | format(session_id, set_id) %}
{% else %}
{% set set_url = "/tema/%d" | format(set_entry.temes[0].tema_id) %}
{% endif %}
<a href="{{ set_url }}"
class="flex flex-row grow items-center cursor-pointer"
title="Mostra els temes">
{% if logged_in %}
<div class="flex flex-row grow items-center">
{% else %}
<a href="{{ set_url }}"
class="flex flex-row grow items-center cursor-pointer"
title="Mostra els temes">
{% endif %}
<div class="flex-1"></div>
<div class="flex flex-col
items-start
@@ -21,7 +25,7 @@
w-full max-w-[655px]">
{% if logged_in %}
<button class="text-beige w-full"
hx-delete="/api/sessio/{{ session_id }}/set/{{ set_id }}"
hx-delete="/api/sessio/{{ session_id }}/playlist/set/{{ set_id }}"
hx-target="#set-entry-{{ set_id }}"
hx-swap="outerHTML">
<i class="fa fa-times" aria-hidden="true"></i>
@@ -40,7 +44,7 @@
</ol>
{% if logged_in %}
<button class="text-beige mt-2 w-full"
hx-post="/api/sessio/{{ session_id }}/set/{{ set_id }}"
hx-post="/api/sessio/{{ session_id }}/playlist/set/{{ set_id }}"
hx-target="#set-entry-{{ set_id }}-list"
hx-swap="beforeend transition:true">
<i class="fa fa-plus" aria-hidden="true"></i>
@@ -49,7 +53,9 @@
{% endif %}
</div>
<div class="flex-1 text-beige">
{% if logged_in %}<a href="{{ set_url }}" class="text-beige">{% endif %}
<i class="fa fa-chevron-right" aria-hidden="true"></i>
{% if logged_in %}</a>{% endif %}
</div>
</a>
{% if logged_in %}</div>{% else %}</a>{% endif %}
</li>

View File

@@ -12,13 +12,13 @@
class="border border-beige focus:outline-none
rounded text-center text-black
p-1 m-1"
hx-get="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}/busca"
hx-get="/api/sessio/{{ session_id }}/playlist/set/{{ set_id }}/tema/{{ tema_entry.id }}/busca"
hx-trigger="revealed, keyup delay:500ms changed"
hx-target="#tune-entry-{{ tema_entry.id }}-search-results"
hx-swap="outerHTML"/>
<button title="Descarta els canvis"
class="text-beige mx-1"
hx-get="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}"
hx-get="/api/sessio/{{ session_id }}/playlist/set/{{ set_id }}/tema/{{ tema_entry.id }}"
hx-target="#tune-entry-{{ tema_entry.id }}"
hx-swap="outerHTML">
<i class="fa fa-times" aria-hidden="true"></i>

View File

@@ -15,14 +15,14 @@
<div class="flex-none flex flex-row shrink-0">
<button title="Edita el tema"
class="text-beige mx-1"
hx-get="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}/editor"
hx-get="/api/sessio/{{ session_id }}/playlist/set/{{ set_id }}/tema/{{ tema_entry.id }}/editor"
hx-target="#tune-entry-{{ tema_entry.id }}"
hx-swap="outerHTML">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
<button title="Esborra el tema"
class="text-beige mx-1"
hx-delete="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}"
hx-delete="/api/sessio/{{ session_id }}/playlist/set/{{ set_id }}/tema/{{ tema_entry.id }}"
hx-target="#tune-entry-{{ tema_entry.id }}"
hx-swap="outerHTML">
<i class="fa fa-times" aria-hidden="true"></i>

View File

@@ -4,7 +4,7 @@
<li>
<button class="bg-beige text-brown rounded
m-1 px-2"
hx-put="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ entry_id }}"
hx-put="/api/sessio/{{ session_id }}/playlist/set/{{ set_id }}/tema/{{ entry_id }}"
hx-vals='{"tema_id": "{{ tema.id }}"}'
hx-target="#tune-entry-{{ entry_id }}"
hx-swap="outerHTML">
@@ -15,7 +15,7 @@
<li>
<button class="border border-beige text-beige rounded
m-1 px-2"
hx-put="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ entry_id }}/unknown"
hx-put="/api/sessio/{{ session_id }}/playlist/set/{{ set_id }}/tema/{{ entry_id }}/unknown"
hx-target="#tune-entry-{{ entry_id }}"
hx-swap="outerHTML">
<i class="fa fa-question" aria-hidden="true"></i>
@@ -25,7 +25,7 @@
<li>
<button class="border border-beige text-beige rounded
m-1 px-2"
hx-post="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ entry_id }}"
hx-post="/api/sessio/{{ session_id }}/playlist/set/{{ set_id }}/tema/{{ entry_id }}"
hx-vals='{"title": "{{ query }}"}'
hx-target="#tune-entry-{{ entry_id }}"
hx-swap="outerHTML">

View File

@@ -4,7 +4,7 @@
class="border border-beige focus:outline-none
rounded grow
bg-brown p-1 my-1"
name='upload_file'>
name='upload_file'/>
<button title="Afegeix un enllaç"
class="border border-beige rounded px-2 py-1 my-1"
hx-get="/api/tema/{{ link.tema_id }}/editor/link/{{ link.id }}/url"

View File

@@ -12,6 +12,7 @@ logger.info(f"Using DB_DIR: {DB_DIR}")
DB_FILES_DIR = DB_DIR / "fitxer"
DB_FILES_TEMA_DIR = DB_FILES_DIR / "tema"
DB_FILES_SESSION_DIR = DB_FILES_DIR / "sessio"
DB_FILES_SET_DIR = DB_FILES_DIR / "set"
DB_FILES_TMP_DIR = DB_FILES_DIR / "tmp"

View File

@@ -59,13 +59,15 @@ def get_playlist_entries(
return map(conversion.row_to_playlist_entry, cur.fetchall())
GetTuneSessionsRow = tuple[int, int, str, str, str, str | None, str | None, bool]
GetTuneSessionsRow = tuple[int, int, str, str, str, str | None, str | None, str | None, str | None, bool]
def get_tune_sessions(tema_ids: list[int], con: Connection | None = None) -> dict[int, list[Session]]:
placeholders = ", ".join(["?" for _ in tema_ids])
query = f"""
SELECT p.tema_id, s.id, s.date, s.start_time, s.end_time, s.venue_name, s.venue_url, s.is_live
SELECT
p.tema_id, s.id, s.date, s.start_time, s.end_time, s.venue_name,
s.venue_url, s.notes, s.cartell_url, s.is_live
FROM playlists p JOIN sessions s ON p.session_id = s.id
WHERE p.tema_id IN ({placeholders})
"""

View File

@@ -3,7 +3,17 @@ from typing import TypedDict
from folkugat_web.model import sessions as model
SessionRowTuple = tuple[int, str, str, str, str | None, str | None, bool]
SessionRowTuple = tuple[
int, # id
str, # date
str, # start_time
str, # end_time
str | None, # venue_name
str | None, # venue_url
str | None, # notes
str | None, # cartell_url
bool, # is_live
]
class SessionRowDict(TypedDict):
@@ -13,6 +23,8 @@ class SessionRowDict(TypedDict):
end_time: str
venue_name: str | None
venue_url: str | None
notes: str | None
cartell_url: str | None
is_live: bool
@@ -24,6 +36,8 @@ def session_to_row(sessio: model.Session) -> SessionRowDict:
'end_time': sessio.end_time.isoformat(),
'venue_name': sessio.venue.name,
'venue_url': sessio.venue.url,
'notes': sessio.notes,
'cartell_url': sessio.cartell_url,
'is_live': sessio.is_live,
}
@@ -38,5 +52,7 @@ def row_to_session(row: SessionRowTuple) -> model.Session:
name=row[4],
url=row[5],
),
is_live=row[6],
notes=row[6],
cartell_url=row[7],
is_live=row[8],
)

View File

@@ -15,6 +15,8 @@ def create_sessions_table(con: Connection):
end_time TEXT NOT NULL,
venue_name TEXT,
venue_url TEXT,
notes TEXT,
cartell_url TEXT,
is_live BOOLEAN DEFAULT false
)
"""

View File

@@ -82,7 +82,7 @@ def get_sessions(session_id: int | None = None,
clauses_str = " ".join(clauses)
query = f"""
SELECT id, date, start_time, end_time, venue_name, venue_url, is_live
SELECT id, date, start_time, end_time, venue_name, venue_url, notes, cartell_url, is_live
FROM sessions
{clauses_str}
"""

View File

@@ -7,9 +7,9 @@ from . import conversion
def insert_session(session: model.Session, con: Connection | None = None):
query = """
INSERT INTO sessions
(id, date, start_time, end_time, venue_name, venue_url, is_live)
(id, date, start_time, end_time, venue_name, venue_url, notes, cartell_url, is_live)
VALUES
(:id, :date, :start_time, :end_time, :venue_name, :venue_url, :is_live)
(:id, :date, :start_time, :end_time, :venue_name, :venue_url, :notes, :cartell_url, :is_live)
RETURNING *
"""
data = conversion.session_to_row(session)
@@ -24,7 +24,8 @@ def update_session(session: model.Session, con: Connection | None = None):
query = """
UPDATE sessions SET
date = :date, start_time = :start_time, end_time = :end_time,
venue_name = :venue_name, venue_url = :venue_url, is_live = :is_live
venue_name = :venue_name, venue_url = :venue_url,
notes = :notes, cartell_url = :cartell_url, is_live = :is_live
WHERE id = :id
"""
data = conversion.session_to_row(session)

View File

@@ -0,0 +1,31 @@
from fastapi import Request
from fastapi.responses import HTMLResponse
from folkugat_web.model.pagines import Pages
from folkugat_web.services import playlists as playlists_service
from folkugat_web.services import sessions as sessions_service
from folkugat_web.services.temes import query as query_service
from folkugat_web.services.temes import search as search_service
from folkugat_web.templates import templates
def cartell(request: Request, session_id: int, logged_in: bool):
session = sessions_service.get_session(session_id=session_id)
return templates.TemplateResponse(
"fragments/sessio/cartell.html",
{
"request": request,
"logged_in": logged_in,
"session_id": session_id,
"session": session,
}
)
def cartell_editor(request: Request, session_id: int):
return templates.TemplateResponse(
"fragments/sessio/cartell_editor.html",
{
"request": request,
"session_id": session_id,
}
)

View File

@@ -0,0 +1,25 @@
from fastapi import HTTPException, Request
from folkugat_web.model.pagines import Pages
from folkugat_web.services import playlists as playlists_service
from folkugat_web.services import sessions as sessions_service
from folkugat_web.templates import templates
def pagina(request: Request, session_id: int, logged_in: bool):
session = sessions_service.get_session(session_id=session_id)
if not session:
raise HTTPException(status_code=404, detail="Could not find session")
playlist = playlists_service.get_playlist(session_id=session_id)
playlist = playlists_service.add_temes_to_playlist(playlist)
return templates.TemplateResponse(
"fragments/sessio/pagina.html",
{
"request": request,
"logged_in": logged_in,
"Pages": Pages,
"session_id": session_id,
"session": session,
"playlist": playlist,
"date_names": sessions_service.get_date_names,
}
)

View File

@@ -8,24 +8,6 @@ from folkugat_web.services.temes import search as search_service
from folkugat_web.templates import templates
def pagina(request: Request, session_id: int, logged_in: bool):
session = sessions_service.get_session(session_id=session_id)
playlist = playlists_service.get_playlist(session_id=session_id)
playlist = playlists_service.add_temes_to_playlist(playlist)
return templates.TemplateResponse(
"fragments/sessio/pagina.html",
{
"request": request,
"logged_in": logged_in,
"Pages": Pages,
"session_id": session_id,
"session": session,
"playlist": playlist,
"date_names": sessions_service.get_date_names,
}
)
def add_set(request: Request, session_id: int, logged_in: bool):
new_set = playlists_service.add_set(session_id=session_id)
return templates.TemplateResponse(

View File

@@ -19,6 +19,8 @@ class Session:
start_time: datetime.time = DEFAULT_START_TIME
end_time: datetime.time = DEFAULT_END_TIME
venue: SessionVenue = dataclasses.field(default_factory=SessionVenue)
notes: str | None = None
cartell_url: str | None = None
is_live: bool = False

View File

@@ -3,7 +3,7 @@ import mimetypes
import os
import re
import uuid
from collections.abc import Iterator
from collections.abc import Iterable, Iterator
from contextlib import asynccontextmanager
from pathlib import Path
@@ -11,6 +11,7 @@ import aiofiles
import magic
from fastapi import HTTPException, UploadFile
from folkugat_web.config import db
from folkugat_web.dal.sql.sessions import query as sessions_dal
from folkugat_web.dal.sql.temes import links as links_dal
from folkugat_web.dal.sql.temes import scores as scores_dal
from folkugat_web.log import logger
@@ -22,22 +23,16 @@ async def get_mimetype(upload_file: UploadFile) -> str:
return info.mime_type
ACCEPTED_MIMETYPES = [
re.compile(r"image/.+"),
re.compile(r".+/pdf"),
]
IMAGE_MIMETYPE = re.compile(r"image/.+")
PDF_MIMETYPE = re.compile(r".+/pdf")
def check_mimetype(mimetype: str) -> None:
if not any(regex.match(mimetype) for regex in ACCEPTED_MIMETYPES):
def check_mimetype(mimetype: str, accepted_mimetypes: Iterable[re.Pattern[str]]) -> None:
if not any(regex.match(mimetype) for regex in accepted_mimetypes):
raise HTTPException(status_code=400, detail=f"Unsupported file type: {mimetype}")
def get_db_file_path(filepath: Path) -> str:
return f"{db.DB_FILES_URL}/{filepath.relative_to(db.DB_FILES_DIR)}"
async def store_file(tema_id: int, upload_file: UploadFile) -> str:
def check_upload_file_size(upload_file: UploadFile) -> None:
if not upload_file.size:
raise HTTPException(status_code=400, detail="Couldn't find out the size of the file")
if upload_file.size > db.FILE_MAX_SIZE:
@@ -46,8 +41,15 @@ async def store_file(tema_id: int, upload_file: UploadFile) -> str:
detail=f"The uploaded file is too big (max size = {db.FILE_MAX_SIZE} bytes)",
)
def get_db_file_path(filepath: Path) -> str:
return f"{db.DB_FILES_URL}/{filepath.relative_to(db.DB_FILES_DIR)}"
async def store_tema_file(tema_id: int, upload_file: UploadFile) -> str:
check_upload_file_size(upload_file)
mimetype = await get_mimetype(upload_file)
check_mimetype(mimetype)
check_mimetype(mimetype, [IMAGE_MIMETYPE, PDF_MIMETYPE])
extension = mimetypes.guess_extension(mimetype) or ""
filepath = create_tema_filename(tema_id=tema_id, extension=extension)
@@ -58,6 +60,20 @@ async def store_file(tema_id: int, upload_file: UploadFile) -> str:
return get_db_file_path(filepath)
async def store_session_cartell(session_id: int, upload_file: UploadFile) -> str:
check_upload_file_size(upload_file)
mimetype = await get_mimetype(upload_file)
check_mimetype(mimetype, [IMAGE_MIMETYPE])
extension = mimetypes.guess_extension(mimetype) or ""
filepath = create_cartell_filename(session_id=session_id, extension=extension)
with open(filepath, "wb") as f:
_ = f.write(await upload_file.read())
return get_db_file_path(filepath)
def create_tema_filename(tema_id: int, extension: str = "") -> Path:
filename = str(uuid.uuid4().hex) + extension
filedir = db.DB_FILES_TEMA_DIR / str(tema_id)
@@ -66,6 +82,14 @@ def create_tema_filename(tema_id: int, extension: str = "") -> Path:
return filepath
def create_cartell_filename(session_id: int, extension: str = "") -> Path:
filename = str(uuid.uuid4().hex) + extension
filedir = db.DB_FILES_SESSION_DIR / str(session_id) / "cartell"
filedir.mkdir(parents=True, exist_ok=True)
filepath = filedir / filename
return filepath
def create_tmp_filename(extension: str = "") -> Path:
filename = str(uuid.uuid4().hex) + extension
filepath = db.DB_FILES_TMP_DIR / filename
@@ -94,11 +118,13 @@ def list_files(tema_id: str) -> list[str]:
def get_orphan_files() -> Iterator[Path]:
link_urls = {link.url for link in links_dal.get_links()}
score_pdf_urls = {score.pdf_url for score in scores_dal.get_scores() if score.pdf_url is not None}
score_img_urls = {score.img_url for score in scores_dal.get_scores() if score.img_url is not None}
score_preview_urls = {score.preview_url for score in scores_dal.get_scores() if score.preview_url is not None}
alive_urls = link_urls | score_pdf_urls | score_img_urls | score_preview_urls
alive_urls = (
{link.url for link in links_dal.get_links()}
| {score.pdf_url for score in scores_dal.get_scores() if score.pdf_url is not None}
| {score.img_url for score in scores_dal.get_scores() if score.img_url is not None}
| {score.preview_url for score in scores_dal.get_scores() if score.preview_url is not None}
| {session.cartell_url for session in sessions_dal.get_sessions() if session.cartell_url}
)
return filter(
lambda p: p.is_file() and get_db_file_path(p) not in alive_urls,
itertools.chain(

View File

@@ -0,0 +1,10 @@
from folkugat_web.dal.sql import get_connection
with get_connection() as con:
cur = con.cursor()
alter_query_1 = """ ALTER TABLE sessions ADD COLUMN notes TEXT """
alter_query_2 = """ ALTER TABLE sessions ADD COLUMN cartell_url TEXT """
_ = cur.execute(alter_query_1)
_ = cur.execute(alter_query_2)
print("DONE!")