Compare commits
13 Commits
badb73098a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
443c1b1391 | ||
|
|
4e7a6b18d6 | ||
|
|
c0624d1e56 | ||
|
|
fdcda1b566 | ||
|
|
ac79785cf0 | ||
|
|
089e61fb0b | ||
|
|
56ab91bd42 | ||
|
|
5428d49e89 | ||
|
|
43706af7c4 | ||
|
|
c1ec268823 | ||
|
|
2fbdbbf290 | ||
|
|
23337f8ab3 | ||
|
|
fbfedf4dac |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
.direnv
|
.direnv
|
||||||
|
|
||||||
**/**.pyc
|
**/**.pyc
|
||||||
|
|
||||||
|
certs
|
||||||
|
|||||||
24
AGENTS.md
Normal file
24
AGENTS.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Development Commands
|
||||||
|
|
||||||
|
## Build/Lint/Test
|
||||||
|
- **Run development server**: `JWT_SECRET=12345 uvicorn folkugat_web.main:app --reload --reload-include "*.html" --host 0.0.0.0`
|
||||||
|
- **Build for production**: `./build.sh`
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
|
||||||
|
### Python
|
||||||
|
- **Type hints**: Use strict typing with `from typing import` and custom types from `folkugat_web.typing`
|
||||||
|
- **Imports**: Standard library first, third-party, then local imports. Use absolute imports for local modules.
|
||||||
|
- **Naming**: snake_case for variables/functions, PascalCase for classes, UPPER_CASE for constants
|
||||||
|
- **Error handling**: Use Result[ResultT, ErrorT] pattern from utils for service functions
|
||||||
|
- **Async**: Use async/await for I/O operations, FastAPI endpoints are async
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- **CSS**: Use Tailwind CSS classes with custom brown/beige theme
|
||||||
|
- **Templates**: Jinja2 templates in folkugat_web/assets/templates/
|
||||||
|
- **Static files**: Organized in folkugat_web/assets/static/
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
- **Structure**: api/routes/, config/, dal/, fragments/, model/, services/
|
||||||
|
- **Database**: SQLite with custom DAL layer in dal/sql/
|
||||||
|
- **Authentication**: JWT-based with cookie storage
|
||||||
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1753432016,
|
"lastModified": 1766125104,
|
||||||
"narHash": "sha256-cnL5WWn/xkZoyH/03NNUS7QgW5vI7D1i74g48qplCvg=",
|
"narHash": "sha256-l/YGrEpLromL4viUo5GmFH3K5M1j0Mb9O+LiaeCPWEM=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "6027c30c8e9810896b92429f0092f624f7b1aace",
|
"rev": "7d853e518814cca2a657b72eeba67ae20ebf7059",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -60,7 +60,10 @@
|
|||||||
lilypond-with-fonts
|
lilypond-with-fonts
|
||||||
# Project tools
|
# Project tools
|
||||||
sqlite
|
sqlite
|
||||||
|
lazysql
|
||||||
opencode
|
opencode
|
||||||
|
# Local https
|
||||||
|
mkcert
|
||||||
];
|
];
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
from folkugat_web.api.router import get_router
|
from folkugat_web.api.router import get_router
|
||||||
|
|
||||||
from . import auth, index, sessio, sessions, tema, temes
|
from . import auth, index, llista, llistes, sessio, sessions, tema, temes
|
||||||
|
|
||||||
router = get_router()
|
router = get_router()
|
||||||
|
|
||||||
|
router.include_router(auth.router)
|
||||||
|
router.include_router(index.router)
|
||||||
|
router.include_router(llista.router)
|
||||||
|
router.include_router(llistes.router)
|
||||||
router.include_router(sessio.router)
|
router.include_router(sessio.router)
|
||||||
router.include_router(sessions.router)
|
router.include_router(sessions.router)
|
||||||
router.include_router(tema.router)
|
router.include_router(tema.router)
|
||||||
router.include_router(temes.router)
|
router.include_router(temes.router)
|
||||||
router.include_router(auth.router)
|
|
||||||
router.include_router(index.router)
|
|
||||||
|
|
||||||
__all__ = ["router"]
|
__all__ = ["router"]
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ def index(request: Request, logged_in: auth.LoggedIn):
|
|||||||
{
|
{
|
||||||
"request": request,
|
"request": request,
|
||||||
"page_title": "Folkugat",
|
"page_title": "Folkugat",
|
||||||
|
"page_description": "Sessions de folk a Sant Cugat",
|
||||||
"content": "/api/content/sessions",
|
"content": "/api/content/sessions",
|
||||||
"logged_in": logged_in,
|
"logged_in": logged_in,
|
||||||
"animate": True,
|
"animate": True,
|
||||||
|
|||||||
9
folkugat_web/api/routes/llista/__init__.py
Normal file
9
folkugat_web/api/routes/llista/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from folkugat_web.api.router import get_router
|
||||||
|
|
||||||
|
from . import playlist, set_page
|
||||||
|
|
||||||
|
router = get_router()
|
||||||
|
router.include_router(set_page.router)
|
||||||
|
router.include_router(playlist.router)
|
||||||
|
|
||||||
|
__all__ = ["router"]
|
||||||
269
folkugat_web/api/routes/llista/playlist.py
Normal file
269
folkugat_web/api/routes/llista/playlist.py
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Form, HTTPException, Request
|
||||||
|
from folkugat_web.api.router import get_router
|
||||||
|
from folkugat_web.fragments import playlist
|
||||||
|
from folkugat_web.services import auth
|
||||||
|
from folkugat_web.services import playlists as playlists_service
|
||||||
|
from folkugat_web.services.temes import write as temes_service
|
||||||
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
|
router = get_router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/llista/{playlist_id}")
|
||||||
|
def page(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.LoggedIn,
|
||||||
|
playlist_id: int,
|
||||||
|
):
|
||||||
|
playlist = playlists_service.get_playlist(playlist_id=playlist_id)
|
||||||
|
if not playlist:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find playlist")
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"index.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"page_title": "Folkugat - Setlist",
|
||||||
|
"page_description": playlist.name or "Llista de temes",
|
||||||
|
"page_card": None,
|
||||||
|
"content": f"/api/content/llista/{playlist_id}",
|
||||||
|
"logged_in": logged_in,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/content/llista/{playlist_id}")
|
||||||
|
async def contingut(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.LoggedIn,
|
||||||
|
playlist_id: int,
|
||||||
|
):
|
||||||
|
return await playlist.pagina(request, playlist_id, logged_in)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/llista/{playlist_id}/name")
|
||||||
|
def get_name(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.LoggedIn,
|
||||||
|
playlist_id: int,
|
||||||
|
):
|
||||||
|
return playlist.name(request=request, playlist_id=playlist_id, logged_in=logged_in)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/llista/{playlist_id}/editor/name")
|
||||||
|
def name_editor(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
playlist_id: int,
|
||||||
|
):
|
||||||
|
return playlist.name_editor(request=request, playlist_id=playlist_id, logged_in=logged_in)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/api/llista/{playlist_id}/name")
|
||||||
|
def set_name(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
playlist_id: int,
|
||||||
|
name: Annotated[str, Form()],
|
||||||
|
):
|
||||||
|
_ = playlists_service.update_name(playlist_id=playlist_id, name=name)
|
||||||
|
return playlist.name(request=request, playlist_id=playlist_id, logged_in=logged_in)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/llista/{playlist_id}/set")
|
||||||
|
def add_set(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
playlist_id: int,
|
||||||
|
):
|
||||||
|
return playlist.add_set(
|
||||||
|
request=request,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
logged_in=logged_in,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/llista/{playlist_id}/set/{set_id}")
|
||||||
|
def get_set(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.LoggedIn,
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
):
|
||||||
|
return playlist.get_set(
|
||||||
|
request=request,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
set_id=set_id,
|
||||||
|
logged_in=logged_in,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/api/llista/{playlist_id}/set/{set_id}")
|
||||||
|
def delete_set(
|
||||||
|
_: auth.RequireLogin,
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
):
|
||||||
|
return playlist.delete_set(
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
set_id=set_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/llista/{playlist_id}/set/{set_id}")
|
||||||
|
def add_tema(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
):
|
||||||
|
return playlist.add_tema(
|
||||||
|
request=request,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
set_id=set_id,
|
||||||
|
logged_in=logged_in,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/llista/{playlist_id}/set/{set_id}/tema/{entry_id}")
|
||||||
|
def get_tema(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
entry_id: int,
|
||||||
|
):
|
||||||
|
return playlist.get_tema(
|
||||||
|
request=request,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
set_id=set_id,
|
||||||
|
entry_id=entry_id,
|
||||||
|
logged_in=logged_in,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/llista/{playlist_id}/set/{set_id}/tema/{entry_id}/editor")
|
||||||
|
def get_tema_editor(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
entry_id: int,
|
||||||
|
):
|
||||||
|
return playlist.get_tema_editor(
|
||||||
|
request=request,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
set_id=set_id,
|
||||||
|
entry_id=entry_id,
|
||||||
|
logged_in=logged_in,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/api/llista/{playlist_id}/set/{set_id}/tema/{entry_id}")
|
||||||
|
def delete_tema(
|
||||||
|
_: auth.RequireLogin,
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
entry_id: int,
|
||||||
|
):
|
||||||
|
return playlist.delete_tema(
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
set_id=set_id,
|
||||||
|
entry_id=entry_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/llista/{playlist_id}/set/{set_id}/tema/{entry_id}/busca")
|
||||||
|
def busca_tema(
|
||||||
|
request: Request,
|
||||||
|
_: auth.RequireLogin,
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
entry_id: int,
|
||||||
|
query: str,
|
||||||
|
):
|
||||||
|
return playlist.busca_tema(
|
||||||
|
request=request,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
set_id=set_id,
|
||||||
|
entry_id=entry_id,
|
||||||
|
query=query,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/api/llista/{playlist_id}/set/{set_id}/tema/{entry_id}")
|
||||||
|
def set_tema(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
entry_id: int,
|
||||||
|
tema_id: Annotated[int, Form()],
|
||||||
|
):
|
||||||
|
return playlist.set_tema(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
set_id=set_id,
|
||||||
|
entry_id=entry_id,
|
||||||
|
tema_id=tema_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/api/llista/{playlist_id}/set/{set_id}/tema/{entry_id}/unknown")
|
||||||
|
def set_tema_unknown(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
entry_id: int,
|
||||||
|
):
|
||||||
|
return playlist.set_tema(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
set_id=set_id,
|
||||||
|
entry_id=entry_id,
|
||||||
|
tema_id=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/llista/{playlist_id}/set/{set_id}/tema/{entry_id}")
|
||||||
|
def set_tema_new(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
playlist_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,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
set_id=set_id,
|
||||||
|
entry_id=entry_id,
|
||||||
|
tema_id=new_tema.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/api/llista/{playlist_id}/visible")
|
||||||
|
def set_visible(request: Request, logged_in: auth.RequireLogin, playlist_id: int):
|
||||||
|
new_playlist = playlists_service.set_visibility(playlist_id=playlist_id, hidden=False)
|
||||||
|
return playlist.visibility(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
playlist=new_playlist,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/api/llista/{playlist_id}/invisible")
|
||||||
|
def set_invisible(request: Request, logged_in: auth.RequireLogin, playlist_id: int):
|
||||||
|
new_playlist = playlists_service.set_visibility(playlist_id=playlist_id, hidden=True)
|
||||||
|
return playlist.visibility(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
playlist=new_playlist,
|
||||||
|
)
|
||||||
55
folkugat_web/api/routes/llista/set_page.py
Normal file
55
folkugat_web/api/routes/llista/set_page.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
from fastapi import HTTPException, Request
|
||||||
|
from folkugat_web.api.router import get_router
|
||||||
|
from folkugat_web.fragments import set_page
|
||||||
|
from folkugat_web.model import playlists
|
||||||
|
from folkugat_web.services import auth
|
||||||
|
from folkugat_web.services import playlists as playlists_service
|
||||||
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
|
router = get_router()
|
||||||
|
|
||||||
|
|
||||||
|
def set_description(set_: playlists.Set) -> str:
|
||||||
|
tema_names = [tis.tema.title if tis.tema else "Desconegut" for tis in set_.temes]
|
||||||
|
return " i ".join(filter(bool, [
|
||||||
|
", ".join(tema_names[:-1]),
|
||||||
|
tema_names[-1]
|
||||||
|
]))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/llista/{playlist_id}/set/{set_id}")
|
||||||
|
def page(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.LoggedIn,
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
):
|
||||||
|
set_ = playlists_service.get_set(playlist_id=playlist_id, set_id=set_id)
|
||||||
|
if not set_:
|
||||||
|
raise HTTPException(status_code=404, detail="Set not found")
|
||||||
|
set_ = playlists_service.add_temes_to_set(set_)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"index.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"page_title": "Folkugat - Set",
|
||||||
|
"page_description": set_description(set_=set_),
|
||||||
|
"content": f"/api/content/llista/{playlist_id}/set/{set_id}",
|
||||||
|
"logged_in": logged_in,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/content/llista/{playlist_id}/set/{set_id}")
|
||||||
|
async def contingut(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.LoggedIn,
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
):
|
||||||
|
return await set_page.pagina(
|
||||||
|
request=request,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
set_id=set_id,
|
||||||
|
logged_in=logged_in,
|
||||||
|
)
|
||||||
5
folkugat_web/api/routes/llistes/__init__.py
Normal file
5
folkugat_web/api/routes/llistes/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from folkugat_web.api.router import get_router
|
||||||
|
from . import index
|
||||||
|
|
||||||
|
router = get_router()
|
||||||
|
router.include_router(index.router)
|
||||||
36
folkugat_web/api/routes/llistes/index.py
Normal file
36
folkugat_web/api/routes/llistes/index.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from fastapi import Request
|
||||||
|
from folkugat_web.api.router import get_router
|
||||||
|
from folkugat_web.fragments import llistes
|
||||||
|
from folkugat_web.services import auth
|
||||||
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
|
router = get_router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/llistes")
|
||||||
|
def page(request: Request, logged_in: auth.LoggedIn):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"index.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"page_title": "Folkugat",
|
||||||
|
"content": "/api/content/llistes",
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"animate": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/content/llistes")
|
||||||
|
def content(request: Request, logged_in: auth.LoggedIn):
|
||||||
|
return llistes.llistes_pagina(request, logged_in)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/llistes/editor/")
|
||||||
|
def insert_row(request: Request, _: auth.RequireLogin):
|
||||||
|
return llistes.llistes_editor_insert_row(request)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/api/llistes/{playlist_id}")
|
||||||
|
def delete_playlist(playlist_id: int, _: auth.RequireLogin):
|
||||||
|
return llistes.llistes_editor_delete_row(playlist_id)
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
from folkugat_web.api.router import get_router
|
from folkugat_web.api.router import get_router
|
||||||
|
|
||||||
from . import index, live, set_page
|
from . import cartell, index, live, notes
|
||||||
|
|
||||||
router = get_router()
|
router = get_router()
|
||||||
|
router.include_router(cartell.router)
|
||||||
router.include_router(index.router)
|
router.include_router(index.router)
|
||||||
router.include_router(live.router)
|
router.include_router(live.router)
|
||||||
router.include_router(set_page.router)
|
router.include_router(notes.router)
|
||||||
|
|
||||||
__all__ = ["router"]
|
__all__ = ["router"]
|
||||||
|
|||||||
71
folkugat_web/api/routes/sessio/cartell.py
Normal file
71
folkugat_web/api/routes/sessio/cartell.py
Normal 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)
|
||||||
@@ -1,26 +1,47 @@
|
|||||||
from typing import Annotated
|
from fastapi import HTTPException, Request
|
||||||
|
|
||||||
from fastapi import Form, Request
|
|
||||||
from folkugat_web.api.router import get_router
|
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.model.sessions import Session
|
||||||
from folkugat_web.services import auth
|
from folkugat_web.services import auth
|
||||||
from folkugat_web.services.temes import write as temes_service
|
from folkugat_web.services import sessions as sessions_service
|
||||||
from folkugat_web.templates import templates
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
router = get_router()
|
router = get_router()
|
||||||
|
|
||||||
|
|
||||||
|
def session_description(session: Session) -> str:
|
||||||
|
date_names = sessions_service.get_date_names(session.date)
|
||||||
|
date_str = f"{date_names.day_name} {date_names.day} {date_names.month_name} de {date_names.year}"
|
||||||
|
location_str = f"A {session.venue.name}" if session.venue else ""
|
||||||
|
time_str = f"de {session.start_time.strftime('%H:%M')} a {session.end_time.strftime('%H:%M')}"
|
||||||
|
location_time_str = " ".join([location_str, time_str])
|
||||||
|
if location_time_str:
|
||||||
|
location_time_str = location_time_str[0].upper() + location_time_str[1:]
|
||||||
|
notes_str = session.notes or ""
|
||||||
|
return "\n".join(filter(bool, [
|
||||||
|
date_str,
|
||||||
|
location_time_str,
|
||||||
|
notes_str,
|
||||||
|
]))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/sessio/{session_id}")
|
@router.get("/sessio/{session_id}")
|
||||||
def page(
|
def page(
|
||||||
request: Request,
|
request: Request,
|
||||||
logged_in: auth.LoggedIn,
|
logged_in: auth.LoggedIn,
|
||||||
session_id: int,
|
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")
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"index.html",
|
"index.html",
|
||||||
{
|
{
|
||||||
"request": request,
|
"request": request,
|
||||||
"page_title": "Folkugat",
|
"page_title": "Folkugat - Sessió",
|
||||||
|
"page_description": session_description(session),
|
||||||
|
"page_card": session.cartell_url,
|
||||||
"content": f"/api/content/sessio/{session_id}",
|
"content": f"/api/content/sessio/{session_id}",
|
||||||
"logged_in": logged_in,
|
"logged_in": logged_in,
|
||||||
}
|
}
|
||||||
@@ -52,150 +73,3 @@ def stop_live(
|
|||||||
session_id: int,
|
session_id: int,
|
||||||
):
|
):
|
||||||
return live.stop_live_session(request=request, session_id=session_id)
|
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,
|
|
||||||
)
|
|
||||||
|
|||||||
47
folkugat_web/api/routes/sessio/notes.py
Normal file
47
folkugat_web/api/routes/sessio/notes.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import dataclasses
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import HTTPException, Request
|
||||||
|
from fastapi.params import Form
|
||||||
|
from folkugat_web.api.router import get_router
|
||||||
|
from folkugat_web.fragments.sessio import notes
|
||||||
|
from folkugat_web.services import auth
|
||||||
|
from folkugat_web.services import sessions as sessions_service
|
||||||
|
|
||||||
|
router = get_router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/sessio/{session_id}/notes")
|
||||||
|
def get_notes(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.LoggedIn,
|
||||||
|
session_id: int,
|
||||||
|
):
|
||||||
|
return notes.notes(request, session_id, logged_in)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/sessio/{session_id}/notes/editor")
|
||||||
|
def get_notes_editor(
|
||||||
|
request: Request,
|
||||||
|
_: auth.RequireLogin,
|
||||||
|
session_id: int,
|
||||||
|
):
|
||||||
|
return notes.notes_editor(request, session_id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/api/sessio/{session_id}/notes")
|
||||||
|
async def set_notes(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
session_id: int,
|
||||||
|
content: Annotated[str, Form()],
|
||||||
|
):
|
||||||
|
session = sessions_service.get_session(session_id=session_id)
|
||||||
|
if not session:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find session")
|
||||||
|
new_session = dataclasses.replace(
|
||||||
|
session,
|
||||||
|
notes=content.strip(),
|
||||||
|
)
|
||||||
|
sessions_service.set_session(new_session)
|
||||||
|
return notes.notes(request, session_id, logged_in)
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
from fastapi import Request
|
|
||||||
from folkugat_web.api.router import get_router
|
|
||||||
from folkugat_web.fragments import set_page
|
|
||||||
from folkugat_web.services import auth
|
|
||||||
from folkugat_web.templates import templates
|
|
||||||
|
|
||||||
router = get_router()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/sessio/{session_id}/set/{set_id}")
|
|
||||||
def page(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.LoggedIn,
|
|
||||||
session_id: int,
|
|
||||||
set_id: int,
|
|
||||||
):
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"index.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"page_title": "Folkugat",
|
|
||||||
"content": f"/api/content/sessio/{session_id}/set/{set_id}",
|
|
||||||
"logged_in": logged_in,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/content/sessio/{session_id}/set/{set_id}")
|
|
||||||
async def contingut(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.LoggedIn,
|
|
||||||
session_id: int,
|
|
||||||
set_id: int,
|
|
||||||
):
|
|
||||||
return await set_page.pagina(request, session_id, set_id, logged_in)
|
|
||||||
@@ -21,13 +21,32 @@ from folkugat_web.utils import FnChain
|
|||||||
router = get_router()
|
router = get_router()
|
||||||
|
|
||||||
|
|
||||||
|
def _build_page_description(tema: Tema) -> str:
|
||||||
|
info_lines = []
|
||||||
|
if composer := tema.composer():
|
||||||
|
info_lines.append(f"Autor: {composer}")
|
||||||
|
if origin := tema.origin():
|
||||||
|
info_lines.append(f"Orígen: {origin}")
|
||||||
|
return "\n".join(info_lines)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/tema/{tema_id}")
|
@router.get("/tema/{tema_id}")
|
||||||
def page(request: Request, logged_in: auth.LoggedIn, tema_id: int):
|
def page(request: Request, logged_in: auth.LoggedIn, tema_id: int):
|
||||||
|
tema = temes_q.get_tema_by_id(tema_id)
|
||||||
|
if not tema:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find tune")
|
||||||
|
tema = (
|
||||||
|
FnChain.transform(tema) |
|
||||||
|
scores_service.add_scores_to_tema |
|
||||||
|
properties_service.add_properties_to_tema
|
||||||
|
).result()
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"index.html",
|
"index.html",
|
||||||
{
|
{
|
||||||
"request": request,
|
"request": request,
|
||||||
"page_title": "Folkugat",
|
"page_title": f"Folkugat - {tema.title}",
|
||||||
|
"page_description": _build_page_description(tema),
|
||||||
|
"page_card": "",
|
||||||
"content": f"/api/tema/{tema_id}",
|
"content": f"/api/tema/{tema_id}",
|
||||||
"logged_in": logged_in,
|
"logged_in": logged_in,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ async def set_link(
|
|||||||
upload_file: Annotated[UploadFile | None, File()] = None,
|
upload_file: Annotated[UploadFile | None, File()] = None,
|
||||||
):
|
):
|
||||||
if upload_file:
|
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 '')
|
link_type = links_service.guess_link_type(url or '')
|
||||||
new_link = model.Link(
|
new_link = model.Link(
|
||||||
@@ -65,7 +65,7 @@ def create_link(
|
|||||||
|
|
||||||
@router.delete("/api/tema/{tema_id}/link/{link_id}")
|
@router.delete("/api/tema/{tema_id}/link/{link_id}")
|
||||||
def delete_link(
|
def delete_link(
|
||||||
_logged_in: auth.RequireLogin,
|
_: auth.RequireLogin,
|
||||||
tema_id: int,
|
tema_id: int,
|
||||||
link_id: int,
|
link_id: int,
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -33,11 +33,25 @@ def set_lyric(
|
|||||||
lyric_id: int,
|
lyric_id: int,
|
||||||
title: Annotated[str, Form()],
|
title: Annotated[str, Form()],
|
||||||
content: Annotated[str, Form()],
|
content: Annotated[str, Form()],
|
||||||
|
max_columns: Annotated[str | None, Form()] = None,
|
||||||
):
|
):
|
||||||
lyric = lyrics_service.get_lyric_by_id(lyric_id=lyric_id, tema_id=tema_id)
|
lyric = lyrics_service.get_lyric_by_id(lyric_id=lyric_id, tema_id=tema_id)
|
||||||
if not lyric:
|
if not lyric:
|
||||||
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||||
new_lyric = lyrics_service.update_lyric(lyric=lyric, title=title, content=content)
|
|
||||||
|
# Parse max_columns from string to int if provided
|
||||||
|
max_columns_int = None
|
||||||
|
if max_columns is not None and max_columns.strip():
|
||||||
|
try:
|
||||||
|
max_columns_int = int(max_columns.strip())
|
||||||
|
except ValueError:
|
||||||
|
raise HTTPException(status_code=400, detail="max_columns must be a valid integer")
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_lyric = lyrics_service.update_lyric(lyric=lyric, title=title, content=content, max_columns=max_columns_int)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
return lyrics_fragments.lyric(request=request, logged_in=logged_in, lyric=new_lyric)
|
return lyrics_fragments.lyric(request=request, logged_in=logged_in, lyric=new_lyric)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,11 +29,16 @@ def page(
|
|||||||
order_params=order_params,
|
order_params=order_params,
|
||||||
)
|
)
|
||||||
content_url = f"/api/content/temes?{temes_params}"
|
content_url = f"/api/content/temes?{temes_params}"
|
||||||
|
if query:
|
||||||
|
additional_description = f" - Resultats per a '{query}'"
|
||||||
|
else:
|
||||||
|
additional_description = ""
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"index.html",
|
"index.html",
|
||||||
{
|
{
|
||||||
"request": request,
|
"request": request,
|
||||||
"page_title": "Folkugat",
|
"page_title": "Folkugat - Temes",
|
||||||
|
"page_description": f"Buscador de temes{additional_description}",
|
||||||
"content": content_url,
|
"content": content_url,
|
||||||
"logged_in": logged_in,
|
"logged_in": logged_in,
|
||||||
"animate": False,
|
"animate": False,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
BIN
folkugat_web/assets/static/img/folkugat_card.jpg
Normal file
BIN
folkugat_web/assets/static/img/folkugat_card.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 218 KiB |
@@ -1,8 +1,10 @@
|
|||||||
<div class="h-4/5 min-h-[400px] flex flex-col items-center justify-center">
|
<div class="h-4/5 min-h-[400px] flex flex-col items-center justify-center">
|
||||||
|
<a href="/">
|
||||||
<img src="{{ url_for(request, 'static', path='img/folkugat.svg') }}"
|
<img src="{{ url_for(request, 'static', path='img/folkugat.svg') }}"
|
||||||
class="{% if animate %} opacity-0 animate-fade-in-one {% endif %} m-3"
|
class="{% if animate %} opacity-0 animate-fade-in-one {% endif %} m-3"
|
||||||
width="100"
|
width="100"
|
||||||
alt="Folkugat"/>
|
alt="Folkugat"/>
|
||||||
<h1 class="text-3xl sm:text-8xl text-beige m-3 {% if animate %} opacity-0 animate-fade-in-one {% endif %}">{{ page_title }}</h1>
|
</a>
|
||||||
|
<h1 class="text-3xl sm:text-8xl text-beige m-3 {% if animate %} opacity-0 animate-fade-in-one {% endif %}">Folkugat</h1>
|
||||||
<h2 class="text-center sm:text-3xl m-6 {% if animate %} opacity-0 animate-fade-in-two {% endif %}">Sessions de folk a Sant Cugat</h2>
|
<h2 class="text-center sm:text-3xl m-6 {% if animate %} opacity-0 animate-fade-in-two {% endif %}">Sessions de folk a Sant Cugat</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<div class="flex flex-row flex-wrap justify-center"
|
||||||
|
id="llista-name">
|
||||||
|
<form>
|
||||||
|
<input name="name"
|
||||||
|
placeholder="Nom de la playlist"
|
||||||
|
value="{{ playlist.name if playlist.name else '' }}"
|
||||||
|
class="border border-beige focus:outline-none
|
||||||
|
rounded text-3xl
|
||||||
|
bg-brown p-2 m-0"
|
||||||
|
/>
|
||||||
|
<button title="Desa"
|
||||||
|
class="text-beige text-3xl mx-1"
|
||||||
|
hx-put="/api/llista/{{ playlist.id }}/name"
|
||||||
|
hx-target="#llista-name"
|
||||||
|
hx-swap="outerHTML">
|
||||||
|
<i class="fa fa-check" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<button title="Descarta"
|
||||||
|
class="text-beige text-3xl mx-1"
|
||||||
|
hx-get="/api/llista/{{ playlist.id }}/name"
|
||||||
|
hx-target="#llista-name"
|
||||||
|
hx-swap="outerHTML">
|
||||||
|
<i class="fa fa-times" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
20
folkugat_web/assets/templates/fragments/llista/name.html
Normal file
20
folkugat_web/assets/templates/fragments/llista/name.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<div class="flex flex-row flex-wrap justify-center"
|
||||||
|
id="llista-name">
|
||||||
|
<h3 class="text-3xl text-center p-4">
|
||||||
|
{% if playlist.name %}
|
||||||
|
{{ playlist.name }}
|
||||||
|
{% else %}
|
||||||
|
Llista de temes
|
||||||
|
{% endif %}
|
||||||
|
</h3>
|
||||||
|
{% if logged_in %}
|
||||||
|
<button title="Canvia el nom"
|
||||||
|
class="text-beige text-2xl mx-2"
|
||||||
|
hx-get="/api/llista/{{ playlist.id }}/editor/name"
|
||||||
|
hx-target="#llista-name"
|
||||||
|
hx-swap="outerHTML">
|
||||||
|
<i class="fa fa-pencil" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
{% include "fragments/llista/visibility.html" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
14
folkugat_web/assets/templates/fragments/llista/pagina.html
Normal file
14
folkugat_web/assets/templates/fragments/llista/pagina.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{% include "fragments/menu.html" %}
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div class="m-12 grow max-w-4xl">
|
||||||
|
<div id="playlist-name">
|
||||||
|
{% include "fragments/llista/name.html" %}
|
||||||
|
</div>
|
||||||
|
<div class="text-left">
|
||||||
|
{% set playlist_id = playlist.id %}
|
||||||
|
{% set playlist = playlist %}
|
||||||
|
{% include "fragments/llista/playlist.html" %}
|
||||||
|
</div>
|
||||||
|
{% include "fragments/llista/score.html" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
<ul id="playlist-{{ session.id }}" class="">
|
<ul id="llista-{{ playlist_id }}" class="">
|
||||||
{% for set_entry in playlist.sets %}
|
{% for set_entry in playlist.sets %}
|
||||||
{% set set_id = set_entry.id %}
|
{% set set_id = set_entry.id %}
|
||||||
{% include "fragments/sessio/set_entry.html" %}
|
{% include "fragments/llista/set_entry.html" %} {% endfor %}
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
</ul>
|
||||||
{% if logged_in %}
|
{% if logged_in %}
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<button class="text-beige mt-2"
|
<button class="text-beige mt-2"
|
||||||
hx-post="/api/sessio/{{ session.id }}/set"
|
hx-post="/api/llista/{{ playlist_id }}/set"
|
||||||
hx-target="#playlist-{{ session.id }}"
|
hx-target="#llista-{{ playlist_id }}"
|
||||||
hx-swap="beforeend transition:true">
|
hx-swap="beforeend transition:true">
|
||||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||||
Afegeix set
|
Afegeix set
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{% if playlist.playlist_score is not none and playlist.playlist_score.pdf_url is not none %}
|
||||||
|
<div class="flex flex-col items-center mt-4 mb-6">
|
||||||
|
<div class="bg-beige border rounded border-beige m-2 p-2">
|
||||||
|
<a href="{{ playlist.playlist_score.pdf_url }}" target="_blank" class="text-white">
|
||||||
|
Obre en PDF
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{% include "fragments/menu.html" %}
|
||||||
|
{% include "fragments/llista/set/set_page.html" %}
|
||||||
@@ -13,14 +13,14 @@
|
|||||||
{% for tema_in_set in set.temes %}
|
{% for tema_in_set in set.temes %}
|
||||||
{% if tema_in_set.tema is not none %}
|
{% if tema_in_set.tema is not none %}
|
||||||
{% set tema = tema_in_set.tema %}
|
{% set tema = tema_in_set.tema %}
|
||||||
{% include "fragments/sessio/set/tema_title.html" %}
|
{% include "fragments/llista/set/tema_title.html" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<h3 class="text-center text-3xl p-4"> <i>Desconegut</i> </h3>
|
<h3 class="text-center text-3xl p-4"> <i>Desconegut</i> </h3>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="mx-12">
|
<div class="mx-12">
|
||||||
<hr class="h-px mt-1 mb-3 bg-beige border-0">
|
<hr class="h-px mt-1 mb-3 bg-beige border-0">
|
||||||
{% include "fragments/sessio/set/set_score.html"%}
|
{% include "fragments/llista/set/set_score.html"%}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for tema_in_set in set.temes %}
|
{% for tema_in_set in set.temes %}
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if tema_in_set.tema is not none %}
|
{% if tema_in_set.tema is not none %}
|
||||||
{% set tema = tema_in_set.tema %}
|
{% set tema = tema_in_set.tema %}
|
||||||
{% include "fragments/sessio/set/tema.html"%}
|
{% include "fragments/llista/set/tema.html"%}
|
||||||
{% else %}
|
{% else %}
|
||||||
<h3 class="text-center text-3xl p-4">
|
<h3 class="text-center text-3xl p-4">
|
||||||
<i>Desconegut</i>
|
<i>Desconegut</i>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{% include "fragments/sessio/set/tema_title.html" %}
|
{% include "fragments/llista/set/tema_title.html" %}
|
||||||
<div class="mx-12 text-left">
|
<div class="mx-12 text-left">
|
||||||
|
|
||||||
{% if tema.main_score() is not none %}
|
{% if tema.main_score() is not none %}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<li class="flex flex-row grow items-center
|
||||||
|
m-4 rounded-lg bg-white
|
||||||
|
px-2 py-1 my-1"
|
||||||
|
id="set-entry-{{ set_id }}"
|
||||||
|
hx-get="/api/llista/{{ playlist_id }}/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].tema %}
|
||||||
|
{% set set_url = "/llista/%d/set/%d" | format(playlist_id, set_id) %}
|
||||||
|
{% else %}
|
||||||
|
{% set set_url = "/tema/%d" | format(set_entry.temes[0].tema_id) %}
|
||||||
|
{% endif %}
|
||||||
|
{% 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
|
||||||
|
py-2 mx-2
|
||||||
|
w-full max-w-[655px]">
|
||||||
|
{% if logged_in %}
|
||||||
|
<button class="text-beige w-full"
|
||||||
|
hx-delete="/api/llista/{{ playlist_id }}/set/{{ set_id }}"
|
||||||
|
hx-target="#set-entry-{{ set_id }}"
|
||||||
|
hx-swap="outerHTML">
|
||||||
|
<i class="fa fa-times" aria-hidden="true"></i>
|
||||||
|
Esborra el set
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
<ol id="set-entry-{{ set_id }}-list"
|
||||||
|
class="flex flex-col items-start w-full">
|
||||||
|
{% for tema_entry in set_entry.temes %}
|
||||||
|
{% if new_entry %}
|
||||||
|
{% include "fragments/llista/tema_editor.html" %}
|
||||||
|
{% else %}
|
||||||
|
{% include "fragments/llista/tema_entry.html" %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
{% if logged_in %}
|
||||||
|
<button class="text-beige mt-2 w-full"
|
||||||
|
hx-post="/api/llista/{{ playlist_id }}/set/{{ set_id }}"
|
||||||
|
hx-target="#set-entry-{{ set_id }}-list"
|
||||||
|
hx-swap="beforeend transition:true">
|
||||||
|
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||||
|
Afegeix un tema
|
||||||
|
</button>
|
||||||
|
{% 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>
|
||||||
|
{% if logged_in %}</div>{% else %}</a>{% endif %}
|
||||||
|
</li>
|
||||||
@@ -12,13 +12,13 @@
|
|||||||
class="border border-beige focus:outline-none
|
class="border border-beige focus:outline-none
|
||||||
rounded text-center text-black
|
rounded text-center text-black
|
||||||
p-1 m-1"
|
p-1 m-1"
|
||||||
hx-get="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}/busca"
|
hx-get="/api/llista/{{ playlist_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}/busca"
|
||||||
hx-trigger="revealed, keyup delay:500ms changed"
|
hx-trigger="revealed, keyup delay:500ms changed"
|
||||||
hx-target="#tune-entry-{{ tema_entry.id }}-search-results"
|
hx-target="#tune-entry-{{ tema_entry.id }}-search-results"
|
||||||
hx-swap="outerHTML"/>
|
hx-swap="outerHTML"/>
|
||||||
<button title="Descarta els canvis"
|
<button title="Descarta els canvis"
|
||||||
class="text-beige mx-1"
|
class="text-beige mx-1"
|
||||||
hx-get="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}"
|
hx-get="/api/llista/{{ playlist_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}"
|
||||||
hx-target="#tune-entry-{{ tema_entry.id }}"
|
hx-target="#tune-entry-{{ tema_entry.id }}"
|
||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML">
|
||||||
<i class="fa fa-times" aria-hidden="true"></i>
|
<i class="fa fa-times" aria-hidden="true"></i>
|
||||||
@@ -15,14 +15,14 @@
|
|||||||
<div class="flex-none flex flex-row shrink-0">
|
<div class="flex-none flex flex-row shrink-0">
|
||||||
<button title="Edita el tema"
|
<button title="Edita el tema"
|
||||||
class="text-beige mx-1"
|
class="text-beige mx-1"
|
||||||
hx-get="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}/editor"
|
hx-get="/api/llista/{{ playlist_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}/editor"
|
||||||
hx-target="#tune-entry-{{ tema_entry.id }}"
|
hx-target="#tune-entry-{{ tema_entry.id }}"
|
||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML">
|
||||||
<i class="fa fa-pencil" aria-hidden="true"></i>
|
<i class="fa fa-pencil" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<button title="Esborra el tema"
|
<button title="Esborra el tema"
|
||||||
class="text-beige mx-1"
|
class="text-beige mx-1"
|
||||||
hx-delete="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}"
|
hx-delete="/api/llista/{{ playlist_id }}/set/{{ set_id }}/tema/{{ tema_entry.id }}"
|
||||||
hx-target="#tune-entry-{{ tema_entry.id }}"
|
hx-target="#tune-entry-{{ tema_entry.id }}"
|
||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML">
|
||||||
<i class="fa fa-times" aria-hidden="true"></i>
|
<i class="fa fa-times" aria-hidden="true"></i>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<button class="bg-beige text-brown rounded
|
<button class="bg-beige text-brown rounded
|
||||||
m-1 px-2"
|
m-1 px-2"
|
||||||
hx-put="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ entry_id }}"
|
hx-put="/api/llista/{{ playlist_id }}/set/{{ set_id }}/tema/{{ entry_id }}"
|
||||||
hx-vals='{"tema_id": "{{ tema.id }}"}'
|
hx-vals='{"tema_id": "{{ tema.id }}"}'
|
||||||
hx-target="#tune-entry-{{ entry_id }}"
|
hx-target="#tune-entry-{{ entry_id }}"
|
||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML">
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<button class="border border-beige text-beige rounded
|
<button class="border border-beige text-beige rounded
|
||||||
m-1 px-2"
|
m-1 px-2"
|
||||||
hx-put="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ entry_id }}/unknown"
|
hx-put="/api/llista/{{ playlist_id }}/set/{{ set_id }}/tema/{{ entry_id }}/unknown"
|
||||||
hx-target="#tune-entry-{{ entry_id }}"
|
hx-target="#tune-entry-{{ entry_id }}"
|
||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML">
|
||||||
<i class="fa fa-question" aria-hidden="true"></i>
|
<i class="fa fa-question" aria-hidden="true"></i>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<button class="border border-beige text-beige rounded
|
<button class="border border-beige text-beige rounded
|
||||||
m-1 px-2"
|
m-1 px-2"
|
||||||
hx-post="/api/sessio/{{ session_id }}/set/{{ set_id }}/tema/{{ entry_id }}"
|
hx-post="/api/llista/{{ playlist_id }}/set/{{ set_id }}/tema/{{ entry_id }}"
|
||||||
hx-vals='{"title": "{{ query }}"}'
|
hx-vals='{"title": "{{ query }}"}'
|
||||||
hx-target="#tune-entry-{{ entry_id }}"
|
hx-target="#tune-entry-{{ entry_id }}"
|
||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML">
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{% if not playlist.hidden %}
|
||||||
|
<button title="Llista visible (amaga-la)"
|
||||||
|
class="text-beige text-2xl mx-2"
|
||||||
|
hx-put="/api/llista/{{ playlist.id }}/invisible"
|
||||||
|
hx-swap="outerHTML">
|
||||||
|
<i class="fa fa-eye" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<button title="Llista invisible (mostra-la)"
|
||||||
|
class="text-beige text-2xl mx-2"
|
||||||
|
hx-put="/api/llista/{{ playlist.id }}/visible"
|
||||||
|
hx-swap="outerHTML">
|
||||||
|
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
26
folkugat_web/assets/templates/fragments/llistes/pagina.html
Normal file
26
folkugat_web/assets/templates/fragments/llistes/pagina.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{% include "fragments/menu.html" %}
|
||||||
|
<div class="p-12 text-center">
|
||||||
|
<h3 class="text-3xl text-beige p-4">Llistes de Repertori</h3>
|
||||||
|
{% if logged_in %}
|
||||||
|
<button title="Afegeix una llista"
|
||||||
|
class="text-beige m-2"
|
||||||
|
hx-post="/api/llistes/editor/"
|
||||||
|
hx-target="#{{ playlist_list_id }}"
|
||||||
|
hx-swap="afterbegin transition:true">
|
||||||
|
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||||
|
Afegeix una llista
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
<div id="{{ playlist_list_id }}"
|
||||||
|
class="flex flex-col items-center">
|
||||||
|
{% if playlists %}
|
||||||
|
{% for playlist in playlists %}
|
||||||
|
{% include "fragments/llistes/playlist_entry.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="text-center py-8">
|
||||||
|
<p class="text-beige/70">No hi ha llistes disponibles.</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<div class="
|
||||||
|
border rounded border-beige
|
||||||
|
p-2 m-2 w-full max-w-xl
|
||||||
|
relative"
|
||||||
|
id="playlist-entry-{{ playlist.id }}">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-xl text-white">
|
||||||
|
<a href="/llista/{{ playlist.id }}">
|
||||||
|
{% if playlist.name %}
|
||||||
|
{{ playlist.name }}
|
||||||
|
{% else %}
|
||||||
|
Llista de temes
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if logged_in %}
|
||||||
|
<div class="ml-auto">
|
||||||
|
{% include "fragments/llistes/playlist_visibility.html" %}
|
||||||
|
<button title="Esborra la llista"
|
||||||
|
class="text-beige mx-1"
|
||||||
|
hx-delete="/api/llistes/{{ playlist.id }}"
|
||||||
|
hx-target="#playlist-entry-{{ playlist.id }}"
|
||||||
|
hx-swap="outerHTML swap:0.5s">
|
||||||
|
<i class="fa fa-times" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{% if not playlist.hidden %}
|
||||||
|
<button title="Llista visible (amaga-la)"
|
||||||
|
class="text-beige mx-2"
|
||||||
|
hx-put="/api/llista/{{ playlist.id }}/invisible"
|
||||||
|
hx-swap="outerHTML">
|
||||||
|
<i class="fa fa-eye" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<button title="Llista invisible (mostra-la)"
|
||||||
|
class="text-beige mx-2"
|
||||||
|
hx-put="/api/llista/{{ playlist.id }}/visible"
|
||||||
|
hx-swap="outerHTML">
|
||||||
|
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
@@ -27,6 +27,19 @@
|
|||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
{% if menu_selected_id == Pages.Llistes %}
|
||||||
|
<li class="p-2 text-beige">
|
||||||
|
<button>
|
||||||
|
{% else %}
|
||||||
|
<li class="p-2">
|
||||||
|
<button hx-get="/api/content/llistes"
|
||||||
|
hx-replace-url="/llistes"
|
||||||
|
hx-target="#content">
|
||||||
|
{% endif %}
|
||||||
|
Llistes
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<hr class="h-px bg-beige border-0">
|
<hr class="h-px bg-beige border-0">
|
||||||
<div hx-get="/api/sessions/live"
|
<div hx-get="/api/sessions/live"
|
||||||
|
|||||||
36
folkugat_web/assets/templates/fragments/sessio/cartell.html
Normal file
36
folkugat_web/assets/templates/fragments/sessio/cartell.html
Normal 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>
|
||||||
@@ -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>
|
||||||
13
folkugat_web/assets/templates/fragments/sessio/notes.html
Normal file
13
folkugat_web/assets/templates/fragments/sessio/notes.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<h4 class="py-4 text-xl text-beige">
|
||||||
|
Notes
|
||||||
|
{% if logged_in %}
|
||||||
|
<button title="Edita les notes"
|
||||||
|
class="mx-1"
|
||||||
|
hx-get="/api/sessio/{{ session_id }}/notes/editor"
|
||||||
|
hx-target="#notes"
|
||||||
|
hx-swap="innerHTML">
|
||||||
|
<i class="fa fa-pencil" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</h4>
|
||||||
|
{{ notes | safe }}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<h4 class="py-4 text-xl text-beige">Notes</h4>
|
||||||
|
<form id="notes-form"
|
||||||
|
class="w-full">
|
||||||
|
<h5 class="text-sm text-beige text-right">
|
||||||
|
<button title="Desa els canvis"
|
||||||
|
class="mx-1"
|
||||||
|
hx-put="/api/sessio/{{session_id}}/notes"
|
||||||
|
hx-target="#notes"
|
||||||
|
hx-swap="innerHTML">
|
||||||
|
<i class="fa fa-check" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<button title="Descarta els canvis"
|
||||||
|
class="mx-1"
|
||||||
|
hx-get="/api/sessio/{{session_id}}/notes"
|
||||||
|
hx-target="#notes"
|
||||||
|
hx-swap="innerHTML">
|
||||||
|
<i class="fa fa-times" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</h5>
|
||||||
|
<hr class="h-px mt-1 mb-3 bg-beige border-0">
|
||||||
|
<textarea name="content"
|
||||||
|
placeholder="Notes"
|
||||||
|
rows="{{ max(notes.count('\n') + 1, 5) }}"
|
||||||
|
class="border border-beige focus:outline-none
|
||||||
|
w-full text-left
|
||||||
|
rounded
|
||||||
|
bg-brown p-2 m-0">{{notes}}</textarea>
|
||||||
|
</form>
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
|
{% include "fragments/sessio/cartell.html" %}
|
||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
<h4 class="pt-4 text-xl text-beige">Horari i lloc</h4>
|
<h4 class="pt-4 text-xl text-beige">Horari i lloc</h4>
|
||||||
De {{ session.start_time.strftime("%H:%M") }}
|
De {{ session.start_time.strftime("%H:%M") }}
|
||||||
@@ -29,10 +30,35 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if logged_in or playlist.sets %}
|
{% if logged_in or session.notes %}
|
||||||
|
<div id="notes"
|
||||||
|
class="text-left">
|
||||||
|
{% set notes = session.notes if session.notes else "" %}
|
||||||
|
{% include "fragments/sessio/notes.html" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if logged_in or (session.slowjam and session.slowjam.sets) %}
|
||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
<h4 class="py-4 text-xl text-beige mt-2">Temes tocats</h4>
|
<h4 class="py-4 text-xl text-beige mt-2">
|
||||||
{% include "fragments/sessio/playlist.html" %}
|
<a href="/llista/{{ session.slowjam.id }}" class="text-beige">
|
||||||
|
Slow Jam
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
{% set playlist_id = session.slowjam.id %}
|
||||||
|
{% set playlist = session.slowjam %}
|
||||||
|
{% include "fragments/llista/playlist.html" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if logged_in or (session.setlist and session.setlist.sets) %}
|
||||||
|
<div class="text-left">
|
||||||
|
<h4 class="py-4 text-xl text-beige mt-2">
|
||||||
|
<a href="/llista/{{ session.setlist.id }}" class="text-beige">
|
||||||
|
Temes tocats
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
{% set playlist_id = session.setlist.id %}
|
||||||
|
{% set playlist = session.setlist %}
|
||||||
|
{% include "fragments/llista/playlist.html" %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
{% include "fragments/menu.html" %}
|
|
||||||
{% include "fragments/sessio/set/set_page.html" %}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<li class="flex flex-row grow items-center
|
|
||||||
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-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] %}
|
|
||||||
{% 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">
|
|
||||||
<div class="flex-1"></div>
|
|
||||||
<div class="flex flex-col
|
|
||||||
items-start
|
|
||||||
py-2 mx-2
|
|
||||||
w-full max-w-[655px]">
|
|
||||||
{% if logged_in %}
|
|
||||||
<button class="text-beige w-full"
|
|
||||||
hx-delete="/api/sessio/{{ session_id }}/set/{{ set_id }}"
|
|
||||||
hx-target="#set-entry-{{ set_id }}"
|
|
||||||
hx-swap="outerHTML">
|
|
||||||
<i class="fa fa-times" aria-hidden="true"></i>
|
|
||||||
Esborra el set
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
<ol id="set-entry-{{ set_id }}-list"
|
|
||||||
class="flex flex-col items-start w-full">
|
|
||||||
{% for tema_entry in set_entry.temes %}
|
|
||||||
{% if new_entry %}
|
|
||||||
{% include "fragments/sessio/tema_editor.html" %}
|
|
||||||
{% else %}
|
|
||||||
{% include "fragments/sessio/tema_entry.html" %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</ol>
|
|
||||||
{% if logged_in %}
|
|
||||||
<button class="text-beige mt-2 w-full"
|
|
||||||
hx-post="/api/sessio/{{ session_id }}/set/{{ set_id }}"
|
|
||||||
hx-target="#set-entry-{{ set_id }}-list"
|
|
||||||
hx-swap="beforeend transition:true">
|
|
||||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
|
||||||
Afegeix un tema
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="flex-1 text-beige">
|
|
||||||
<i class="fa fa-chevron-right" aria-hidden="true"></i>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
class="border border-beige focus:outline-none
|
class="border border-beige focus:outline-none
|
||||||
rounded grow
|
rounded grow
|
||||||
bg-brown p-1 my-1"
|
bg-brown p-1 my-1"
|
||||||
name='upload_file'>
|
name='upload_file'/>
|
||||||
<button title="Afegeix un enllaç"
|
<button title="Afegeix un enllaç"
|
||||||
class="border border-beige rounded px-2 py-1 my-1"
|
class="border border-beige rounded px-2 py-1 my-1"
|
||||||
hx-get="/api/tema/{{ link.tema_id }}/editor/link/{{ link.id }}/url"
|
hx-get="/api/tema/{{ link.tema_id }}/editor/link/{{ link.id }}/url"
|
||||||
|
|||||||
@@ -7,6 +7,16 @@
|
|||||||
rounded
|
rounded
|
||||||
bg-brown px-2 "
|
bg-brown px-2 "
|
||||||
/>
|
/>
|
||||||
|
<input name="max_columns"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="10"
|
||||||
|
placeholder="3 (default)"
|
||||||
|
value="{{ lyric.max_columns or '' }}"
|
||||||
|
class="border border-beige focus:outline-none
|
||||||
|
rounded
|
||||||
|
bg-brown px-2 ml-2 w-16 "
|
||||||
|
/>
|
||||||
<button title="Desa els canvis"
|
<button title="Desa els canvis"
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
hx-put="/api/tema/{{ lyric.tema_id }}/lyric/{{ lyric.id }}"
|
hx-put="/api/tema/{{ lyric.tema_id }}/lyric/{{ lyric.id }}"
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<div id="tema-lyric-{{ lyric.id }}">
|
<div id="tema-lyric-{{ lyric.id }}">
|
||||||
<h5 class="text-sm text-beige text-right">
|
<h5 class="text-sm text-beige text-right">
|
||||||
{{ lyric.title }}
|
{{ lyric.title }}
|
||||||
|
{% if logged_in and lyric.max_columns %}
|
||||||
|
<span class="text-xs ml-2">{{ lyric.max_columns }} columnes</span>
|
||||||
|
{% endif %}
|
||||||
{% if logged_in %}
|
{% if logged_in %}
|
||||||
<button title="Modifica la lletra"
|
<button title="Modifica la lletra"
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
@@ -20,6 +23,6 @@
|
|||||||
</h5>
|
</h5>
|
||||||
<hr class="h-px mt-1 mb-3 bg-beige border-0">
|
<hr class="h-px mt-1 mb-3 bg-beige border-0">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
{{ lyric.content.replace('\n', '<br>') | safe }}
|
{{ (lyric.content.replace('~', '')).replace('\n', '<br>') | safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,16 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/>
|
||||||
|
|
||||||
|
<!-- Essential Open Graph (Facebook, WhatsApp, etc.) -->
|
||||||
|
<meta property="og:title" content="{{ page_title }}">
|
||||||
|
<meta property="og:description" content="{{ page_description }}">
|
||||||
|
<meta property="og:image" content="{{ page_card or url_for(request, 'static', path='img/folkugat_card.jpg') }}">
|
||||||
|
|
||||||
|
<!-- Twitter Card (optional but recommended) -->
|
||||||
|
<meta name="twitter:title" content="{{ page_title }}">
|
||||||
|
<meta name="twitter:description" content="{{ page_description }}">
|
||||||
|
<meta name="twitter:image" content="{{ page_card or url_for(request, 'static', path='img/folkugat_card.jpg') }}">
|
||||||
|
|
||||||
<title>{{ page_title }}</title>
|
<title>{{ page_title }}</title>
|
||||||
<meta name="description" content="{{ page_description }}"/>
|
<meta name="description" content="{{ page_description }}"/>
|
||||||
|
|
||||||
|
|||||||
84
folkugat_web/assets/templates/lilypond/lib.ly
Normal file
84
folkugat_web/assets/templates/lilypond/lib.ly
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
% -------------------------------------------------------------------------
|
||||||
|
% Extra custom functions
|
||||||
|
% -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
% Alternative chords
|
||||||
|
|
||||||
|
#(define (pitches-bass-inversion-context-list music context)
|
||||||
|
"Creates a list containing relevant pitches, bass, inversion and context to
|
||||||
|
serve as argument for `ignatzek-chord-names', derived from @var{music}
|
||||||
|
"
|
||||||
|
(let* (;; work on a copy of `music', warranting not to change it
|
||||||
|
(m-add (ly:music-deep-copy music))
|
||||||
|
(ev-notes-orig (event-chord-notes m-add))
|
||||||
|
;; if chord is an inversion some pitches are below root, in this case
|
||||||
|
;; 'octavation-property is set.
|
||||||
|
;; reset pitch of them
|
||||||
|
;; TODO this is an ugly hack, find better method
|
||||||
|
(ev-notes
|
||||||
|
(map
|
||||||
|
(lambda (m)
|
||||||
|
(if (and (not (null? (ly:music-property m 'octavation)))
|
||||||
|
(= (ly:music-property m 'octavation) -1))
|
||||||
|
(begin
|
||||||
|
(ly:music-set-property! m 'pitch
|
||||||
|
(ly:pitch-transpose
|
||||||
|
(ly:music-property m 'pitch)
|
||||||
|
(ly:make-pitch 1 0 0)))
|
||||||
|
m)
|
||||||
|
m))
|
||||||
|
ev-notes-orig))
|
||||||
|
;; sort the ptches ascending as expected by `ignatzek-chord-names'
|
||||||
|
(ev-pitches
|
||||||
|
(sort
|
||||||
|
(map (lambda (m) (ly:music-property m 'pitch)) ev-notes)
|
||||||
|
ly:pitch<?))
|
||||||
|
;; get bass
|
||||||
|
(bass-note
|
||||||
|
(filter
|
||||||
|
(lambda (m)
|
||||||
|
(not (null? (ly:music-property m 'bass))))
|
||||||
|
ev-notes))
|
||||||
|
(bass-pitch
|
||||||
|
(if (not (null? bass-note))
|
||||||
|
(ly:music-property (car bass-note) 'pitch)
|
||||||
|
'()))
|
||||||
|
;; get inversion
|
||||||
|
(inversion-note
|
||||||
|
(filter
|
||||||
|
(lambda (m)
|
||||||
|
(not (null? (ly:music-property m 'inversion))))
|
||||||
|
ev-notes))
|
||||||
|
(inversion-pitch
|
||||||
|
(if (not (null? inversion-note))
|
||||||
|
(ly:music-property (car inversion-note) 'pitch)
|
||||||
|
'()))
|
||||||
|
;; TODO why is this needed?
|
||||||
|
(in-pitches
|
||||||
|
(if (null? inversion-pitch)
|
||||||
|
(delq bass-pitch ev-pitches)
|
||||||
|
ev-pitches)))
|
||||||
|
|
||||||
|
(list in-pitches bass-pitch inversion-pitch context)))
|
||||||
|
|
||||||
|
altChords =
|
||||||
|
#(define-music-function (m1 m2)(ly:music? ly:music?)
|
||||||
|
"Return the default ChordName of @var{m1}, with an added parenthesized ChordName
|
||||||
|
derived from @var{m2}"
|
||||||
|
#{
|
||||||
|
\applyOutput ChordNames.ChordName
|
||||||
|
#(lambda (g ctx p)
|
||||||
|
(let ((main-text (ly:grob-property g 'text))
|
||||||
|
(alt-text
|
||||||
|
(apply
|
||||||
|
ignatzek-chord-names
|
||||||
|
(pitches-bass-inversion-context-list m2 ctx))))
|
||||||
|
(ly:grob-set-property! g 'text
|
||||||
|
#{
|
||||||
|
\markup
|
||||||
|
{ $main-text \hspace #0.4 \fontsize #-3 \parenthesize $alt-text }
|
||||||
|
#})))
|
||||||
|
$m1
|
||||||
|
#})
|
||||||
|
|
||||||
|
% -------------------------------------------------------------------------
|
||||||
49
folkugat_web/assets/templates/lilypond/playlist.ly
Normal file
49
folkugat_web/assets/templates/lilypond/playlist.ly
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{{ score_beginning }}
|
||||||
|
\version "2.24.4"
|
||||||
|
|
||||||
|
{% include "lilypond/lib.ly" %}
|
||||||
|
|
||||||
|
\book {
|
||||||
|
\paper {
|
||||||
|
top-margin = 10
|
||||||
|
left-margin = 15
|
||||||
|
right-margin = 15
|
||||||
|
ragged-bottom = ##f
|
||||||
|
|
||||||
|
scoreTitleMarkup = \markup {
|
||||||
|
\center-column {
|
||||||
|
\fontsize #3 \bold \fromproperty #'header:piece
|
||||||
|
\fill-line {
|
||||||
|
\null
|
||||||
|
\right-column {
|
||||||
|
\fromproperty #'header:composer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
\header {
|
||||||
|
title = \markup { "{{ playlist.title | safe }}" }
|
||||||
|
tagline = "Partitura generada amb LilyPond"
|
||||||
|
copyright = "Folkugat"
|
||||||
|
}
|
||||||
|
|
||||||
|
{% for set in playlist.sets %}
|
||||||
|
{% if set.tunes|length > 1 %}
|
||||||
|
\markup {
|
||||||
|
\vspace #2
|
||||||
|
\fill-line {
|
||||||
|
\center-column {
|
||||||
|
\fontsize #2 \bold "{{ set.title | safe }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\noPageBreak
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for tune in set.tunes %}
|
||||||
|
{% include "lilypond/tune_in_set.ly" %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
\version "2.24.4"
|
\version "2.24.4"
|
||||||
|
{% include "lilypond/lib.ly" %}
|
||||||
{% if tune.score_source is not none %}
|
{% if tune.score_source is not none %}
|
||||||
\score {
|
\score {
|
||||||
\language "english"
|
\language "english"
|
||||||
|
|||||||
@@ -7,13 +7,25 @@
|
|||||||
% -----------------------------------------------------
|
% -----------------------------------------------------
|
||||||
|
|
||||||
% A
|
% A
|
||||||
|
\repeat volta 2 {
|
||||||
|
\alternative {
|
||||||
|
\volta 1 { }
|
||||||
|
\volta 2 { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
% B
|
% B
|
||||||
|
\repeat volta 2 {
|
||||||
|
\alternative {
|
||||||
|
\volta 1 { }
|
||||||
|
\volta 2 { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
\new Staff {
|
\new Staff {
|
||||||
\relative {
|
\relative c' {
|
||||||
% -----------------------------------------------------
|
% -----------------------------------------------------
|
||||||
% Compàs
|
% Compàs
|
||||||
% -----------------------------------------------------
|
% -----------------------------------------------------
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
\version "2.24.4"
|
\version "2.24.4"
|
||||||
|
|
||||||
|
{% include "lilypond/lib.ly" %}
|
||||||
|
|
||||||
\paper {
|
\paper {
|
||||||
top-margin = 10
|
top-margin = 10
|
||||||
left-margin = 15
|
left-margin = 15
|
||||||
@@ -35,7 +38,7 @@
|
|||||||
\hspace #1
|
\hspace #1
|
||||||
\center-column {
|
\center-column {
|
||||||
{% for line in paragraph.lines %}
|
{% for line in paragraph.lines %}
|
||||||
\line { {{ line | safe }} }
|
{% if line %} \line { {{ line | safe }} } {% else %} \vspace #1 {% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
}
|
}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
\noPageBreak
|
||||||
{% if tune.score_source is not none %}
|
{% if tune.score_source is not none %}
|
||||||
\score {
|
\score {
|
||||||
\language "english"
|
\language "english"
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
{{ score_beginning }}
|
{{ score_beginning }}
|
||||||
\version "2.24.4"
|
\version "2.24.4"
|
||||||
|
|
||||||
|
{% include "lilypond/lib.ly" %}
|
||||||
|
|
||||||
\book {
|
\book {
|
||||||
\paper {
|
\paper {
|
||||||
top-margin = 10
|
top-margin = 10
|
||||||
|
|||||||
@@ -12,13 +12,16 @@ logger.info(f"Using DB_DIR: {DB_DIR}")
|
|||||||
|
|
||||||
DB_FILES_DIR = DB_DIR / "fitxer"
|
DB_FILES_DIR = DB_DIR / "fitxer"
|
||||||
DB_FILES_TEMA_DIR = DB_FILES_DIR / "tema"
|
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_SET_DIR = DB_FILES_DIR / "set"
|
||||||
|
DB_FILES_PLAYLIST_DIR = DB_FILES_DIR / "llistes"
|
||||||
DB_FILES_TMP_DIR = DB_FILES_DIR / "tmp"
|
DB_FILES_TMP_DIR = DB_FILES_DIR / "tmp"
|
||||||
|
|
||||||
for path in [
|
for path in [
|
||||||
DB_FILES_DIR,
|
DB_FILES_DIR,
|
||||||
DB_FILES_TEMA_DIR,
|
DB_FILES_TEMA_DIR,
|
||||||
DB_FILES_SET_DIR,
|
DB_FILES_SET_DIR,
|
||||||
|
DB_FILES_PLAYLIST_DIR,
|
||||||
DB_FILES_SET_DIR,
|
DB_FILES_SET_DIR,
|
||||||
]:
|
]:
|
||||||
path.mkdir(exist_ok=True)
|
path.mkdir(exist_ok=True)
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ from typing import TypedDict
|
|||||||
|
|
||||||
from folkugat_web.model import playlists as model
|
from folkugat_web.model import playlists as model
|
||||||
|
|
||||||
PlaylistRowTuple = tuple[int, int, int, int | None]
|
PlaylistRowTuple = tuple[int, str | None, int]
|
||||||
|
PlaylistEntryRowTuple = tuple[int, int, int, int | None]
|
||||||
|
|
||||||
|
|
||||||
class PlaylistRowDict(TypedDict):
|
class PlaylistRowDict(TypedDict):
|
||||||
id: int | None
|
id: int | None
|
||||||
session_id: int
|
playlist_id: int
|
||||||
set_id: int
|
set_id: int
|
||||||
tema_id: int | None
|
tema_id: int | None
|
||||||
|
|
||||||
@@ -15,16 +16,25 @@ class PlaylistRowDict(TypedDict):
|
|||||||
def playlist_entry_to_row(tema_in_set: model.PlaylistEntry) -> PlaylistRowDict:
|
def playlist_entry_to_row(tema_in_set: model.PlaylistEntry) -> PlaylistRowDict:
|
||||||
return {
|
return {
|
||||||
'id': tema_in_set.id,
|
'id': tema_in_set.id,
|
||||||
'session_id': tema_in_set.session_id,
|
'playlist_id': tema_in_set.playlist_id,
|
||||||
'set_id': tema_in_set.set_id,
|
'set_id': tema_in_set.set_id,
|
||||||
'tema_id': tema_in_set.tema_id,
|
'tema_id': tema_in_set.tema_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def row_to_playlist_entry(row: PlaylistRowTuple) -> model.PlaylistEntry:
|
def row_to_playlist_entry(row: PlaylistEntryRowTuple) -> model.PlaylistEntry:
|
||||||
return model.PlaylistEntry(
|
return model.PlaylistEntry(
|
||||||
id=row[0],
|
id=row[0],
|
||||||
session_id=row[1],
|
playlist_id=row[1],
|
||||||
set_id=row[2],
|
set_id=row[2],
|
||||||
tema_id=row[3],
|
tema_id=row[3],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def row_to_playlist(row: PlaylistRowTuple) -> model.Playlist:
|
||||||
|
return model.Playlist(
|
||||||
|
id=row[0],
|
||||||
|
name=row[1],
|
||||||
|
sets=[], # Empty for list view
|
||||||
|
hidden=bool(row[2]),
|
||||||
|
)
|
||||||
|
|||||||
@@ -4,15 +4,30 @@ from folkugat_web.dal.sql import Connection, get_connection
|
|||||||
def create_db(con: Connection | None = None):
|
def create_db(con: Connection | None = None):
|
||||||
with get_connection(con) as con:
|
with get_connection(con) as con:
|
||||||
create_playlists_table(con)
|
create_playlists_table(con)
|
||||||
|
create_playlist_entries_table(con)
|
||||||
|
|
||||||
|
|
||||||
def create_playlists_table(con: Connection):
|
def create_playlists_table(con: Connection):
|
||||||
query = """
|
query = """
|
||||||
CREATE TABLE IF NOT EXISTS playlists (
|
CREATE TABLE IF NOT EXISTS playlists (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
session_id INTEGER NOT NULL,
|
name TEXT,
|
||||||
set_id INTEGER NOT NULL,
|
hidden INTEGER NOT NULL DEFAULT 1
|
||||||
tema_id INTEGER
|
)
|
||||||
|
"""
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query)
|
||||||
|
|
||||||
|
|
||||||
|
def create_playlist_entries_table(con: Connection):
|
||||||
|
query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS playlist_entries (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
playlist_id INTEGER NOT NULL,
|
||||||
|
set_id INTEGER NOT NULL,
|
||||||
|
tema_id INTEGER,
|
||||||
|
FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (tema_id) REFERENCES temes(id) ON DELETE CASCADE
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
cur = con.cursor()
|
cur = con.cursor()
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
from collections.abc import Iterable, Iterator
|
from collections.abc import Iterator
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
from folkugat_web.dal.sql import Connection, get_connection
|
from folkugat_web.dal.sql import Connection, get_connection
|
||||||
from folkugat_web.dal.sql.sessions import conversion as sessions_conversion
|
from folkugat_web.dal.sql.sessions import conversion as sessions_conversion
|
||||||
from folkugat_web.dal.sql.temes.conversion import row_to_tema
|
|
||||||
from folkugat_web.model import playlists as model
|
from folkugat_web.model import playlists as model
|
||||||
from folkugat_web.model import temes as temes_model
|
|
||||||
from folkugat_web.model.sessions import Session
|
from folkugat_web.model.sessions import Session
|
||||||
from folkugat_web.utils import groupby
|
from folkugat_web.utils import groupby
|
||||||
|
|
||||||
@@ -15,13 +13,13 @@ from . import conversion
|
|||||||
class QueryData(TypedDict, total=False):
|
class QueryData(TypedDict, total=False):
|
||||||
id: int
|
id: int
|
||||||
set_id: int
|
set_id: int
|
||||||
session_id: int
|
playlist_id: int
|
||||||
|
|
||||||
|
|
||||||
def _filter_clause(
|
def _filter_clause(
|
||||||
entry_id: int | None = None,
|
entry_id: int | None = None,
|
||||||
set_id: int | None = None,
|
set_id: int | None = None,
|
||||||
session_id: int | None = None,
|
playlist_id: int | None = None,
|
||||||
) -> tuple[str, QueryData]:
|
) -> tuple[str, QueryData]:
|
||||||
filter_clauses: list[str] = []
|
filter_clauses: list[str] = []
|
||||||
query_data: QueryData = {}
|
query_data: QueryData = {}
|
||||||
@@ -32,9 +30,9 @@ def _filter_clause(
|
|||||||
if set_id is not None:
|
if set_id is not None:
|
||||||
filter_clauses.append("set_id = :set_id")
|
filter_clauses.append("set_id = :set_id")
|
||||||
query_data["set_id"] = set_id
|
query_data["set_id"] = set_id
|
||||||
if session_id is not None:
|
if playlist_id is not None:
|
||||||
filter_clauses.append("session_id = :session_id")
|
filter_clauses.append("playlist_id = :playlist_id")
|
||||||
query_data["session_id"] = session_id
|
query_data["playlist_id"] = playlist_id
|
||||||
|
|
||||||
return " AND ".join(filter_clauses), query_data
|
return " AND ".join(filter_clauses), query_data
|
||||||
|
|
||||||
@@ -42,14 +40,14 @@ def _filter_clause(
|
|||||||
def get_playlist_entries(
|
def get_playlist_entries(
|
||||||
entry_id: int | None = None,
|
entry_id: int | None = None,
|
||||||
set_id: int | None = None,
|
set_id: int | None = None,
|
||||||
session_id: int | None = None,
|
playlist_id: int | None = None,
|
||||||
con: Connection | None = None,
|
con: Connection | None = None,
|
||||||
) -> Iterator[model.PlaylistEntry]:
|
) -> Iterator[model.PlaylistEntry]:
|
||||||
filter_clause, data = _filter_clause(entry_id=entry_id, set_id=set_id, session_id=session_id)
|
filter_clause, data = _filter_clause(entry_id=entry_id, set_id=set_id, playlist_id=playlist_id)
|
||||||
query = f"""
|
query = f"""
|
||||||
SELECT
|
SELECT
|
||||||
id, session_id, set_id, tema_id
|
id, playlist_id, set_id, tema_id
|
||||||
FROM playlists
|
FROM playlist_entries
|
||||||
WHERE {filter_clause}
|
WHERE {filter_clause}
|
||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
"""
|
"""
|
||||||
@@ -59,55 +57,54 @@ def get_playlist_entries(
|
|||||||
return map(conversion.row_to_playlist_entry, cur.fetchall())
|
return map(conversion.row_to_playlist_entry, cur.fetchall())
|
||||||
|
|
||||||
|
|
||||||
GetTuneSessionsRow = tuple[int, int, str, str, str, str | None, str | None, bool]
|
def get_playlist_name(playlist_id: int, con: Connection | None = None) -> str | None:
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
FROM playlists p JOIN sessions s ON p.session_id = s.id
|
|
||||||
WHERE p.tema_id IN ({placeholders})
|
|
||||||
"""
|
|
||||||
with get_connection(con) as con:
|
|
||||||
cur = con.cursor()
|
|
||||||
_ = cur.execute(query, tema_ids)
|
|
||||||
result_rows: Iterable[GetTuneSessionsRow] = cur.fetchall()
|
|
||||||
return dict(groupby(
|
|
||||||
result_rows,
|
|
||||||
key_fn=lambda row: row[0],
|
|
||||||
group_fn=lambda rows: list(sessions_conversion.row_to_session(row[1:]) for row in rows)
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
CommonlyPlayedTuneRow = tuple[int, str, str, str, str, int, int]
|
|
||||||
|
|
||||||
|
|
||||||
def get_commonly_played_tunes(
|
|
||||||
tema_id: int,
|
|
||||||
con: Connection | None = None,
|
|
||||||
) -> list[temes_model.CommonlyPlayedTema]:
|
|
||||||
query = """
|
query = """
|
||||||
SELECT
|
SELECT name
|
||||||
id, title, alternatives, creation_date, modification_date, hidden, count
|
|
||||||
FROM (
|
|
||||||
SELECT tema_id, count(*) count FROM playlists p JOIN (
|
|
||||||
SELECT session_id, set_id
|
|
||||||
FROM playlists
|
FROM playlists
|
||||||
WHERE tema_id = ?
|
WHERE id = :playlist_id
|
||||||
) s
|
|
||||||
ON p.session_id == s.session_id AND p.set_id == s.set_id
|
|
||||||
WHERE tema_id != ?
|
|
||||||
GROUP BY tema_id
|
|
||||||
) common JOIN temes t ON common.tema_id == t.id
|
|
||||||
"""
|
"""
|
||||||
|
data = dict(playlist_id=playlist_id)
|
||||||
with get_connection(con) as con:
|
with get_connection(con) as con:
|
||||||
cur = con.cursor()
|
cur = con.cursor()
|
||||||
_ = cur.execute(query, [tema_id, tema_id])
|
_ = cur.execute(query, data)
|
||||||
result_rows: Iterable[CommonlyPlayedTuneRow] = cur.fetchall()
|
row = cur.fetchone()
|
||||||
return [
|
return row[0] if row else None
|
||||||
temes_model.CommonlyPlayedTema(
|
|
||||||
tema=row_to_tema(row[:6]),
|
|
||||||
count=row[6],
|
def get_all_playlists(logged_in: bool = False, con: Connection | None = None) -> Iterator[model.Playlist]:
|
||||||
) for row in result_rows
|
if logged_in:
|
||||||
]
|
# Show all playlists for logged in users, except session-associated ones
|
||||||
|
query = """
|
||||||
|
SELECT id, name, hidden
|
||||||
|
FROM playlists
|
||||||
|
WHERE id NOT IN (
|
||||||
|
SELECT DISTINCT playlist_id
|
||||||
|
FROM session_playlists
|
||||||
|
)
|
||||||
|
ORDER BY id DESC
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
# Show only visible playlists for non-logged in users, except session-associated ones
|
||||||
|
query = """
|
||||||
|
SELECT id, name, hidden
|
||||||
|
FROM playlists
|
||||||
|
WHERE hidden = 0 AND id NOT IN (
|
||||||
|
SELECT DISTINCT playlist_id
|
||||||
|
FROM session_playlists
|
||||||
|
)
|
||||||
|
ORDER BY id DESC
|
||||||
|
"""
|
||||||
|
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
# Convert to Playlist model without sets for list view
|
||||||
|
yield model.Playlist(
|
||||||
|
id=row[0],
|
||||||
|
name=row[1],
|
||||||
|
sets=[], # Empty sets list for now
|
||||||
|
hidden=bool(row[2])
|
||||||
|
)
|
||||||
|
|||||||
@@ -4,27 +4,66 @@ from folkugat_web.model import playlists as model
|
|||||||
from . import conversion
|
from . import conversion
|
||||||
|
|
||||||
|
|
||||||
def insert_playlist_entry(pl_entry: model.PlaylistEntry, con: Connection | None = None) -> model.PlaylistEntry:
|
def create_playlist(
|
||||||
|
name: str | None = None,
|
||||||
|
con: Connection | None = None,
|
||||||
|
) -> int:
|
||||||
query = """
|
query = """
|
||||||
INSERT INTO playlists
|
INSERT INTO playlists
|
||||||
(id, session_id, set_id, tema_id)
|
(name)
|
||||||
VALUES
|
VALUES
|
||||||
(:id, :session_id, :set_id, :tema_id)
|
(:name)
|
||||||
|
RETURNING *
|
||||||
|
"""
|
||||||
|
data = dict(name=name)
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
row: conversion.PlaylistRowTuple = cur.fetchone()
|
||||||
|
return row[0]
|
||||||
|
|
||||||
|
|
||||||
|
def delete_playlist(
|
||||||
|
playlist_id: int,
|
||||||
|
con: Connection | None = None,
|
||||||
|
) -> None:
|
||||||
|
query = """
|
||||||
|
DELETE FROM playlists
|
||||||
|
WHERE id = :id
|
||||||
|
"""
|
||||||
|
data = dict(id=playlist_id)
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
|
||||||
|
|
||||||
|
def insert_playlist_entry(
|
||||||
|
pl_entry: model.PlaylistEntry,
|
||||||
|
con: Connection | None = None,
|
||||||
|
) -> model.PlaylistEntry:
|
||||||
|
query = """
|
||||||
|
INSERT INTO playlist_entries
|
||||||
|
(id, playlist_id, set_id, tema_id)
|
||||||
|
VALUES
|
||||||
|
(:id, :playlist_id, :set_id, :tema_id)
|
||||||
RETURNING *
|
RETURNING *
|
||||||
"""
|
"""
|
||||||
data = conversion.playlist_entry_to_row(pl_entry)
|
data = conversion.playlist_entry_to_row(pl_entry)
|
||||||
with get_connection(con) as con:
|
with get_connection(con) as con:
|
||||||
cur = con.cursor()
|
cur = con.cursor()
|
||||||
_ = cur.execute(query, data)
|
_ = cur.execute(query, data)
|
||||||
row: conversion.PlaylistRowTuple = cur.fetchone()
|
row: conversion.PlaylistEntryRowTuple = cur.fetchone()
|
||||||
return conversion.row_to_playlist_entry(row)
|
return conversion.row_to_playlist_entry(row)
|
||||||
|
|
||||||
|
|
||||||
def update_playlist_entry(entry: model.PlaylistEntry, con: Connection | None = None):
|
def update_playlist_entry(
|
||||||
|
entry: model.PlaylistEntry,
|
||||||
|
con: Connection | None = None,
|
||||||
|
):
|
||||||
query = """
|
query = """
|
||||||
UPDATE playlists
|
UPDATE playlist_entries
|
||||||
SET
|
SET
|
||||||
id = :id, session_id = :session_id, set_id = :set_id, tema_id = :tema_id
|
id = :id, playlist_id = :playlist_id, set_id = :set_id, tema_id = :tema_id
|
||||||
WHERE
|
WHERE
|
||||||
id = :id
|
id = :id
|
||||||
"""
|
"""
|
||||||
@@ -35,9 +74,12 @@ def update_playlist_entry(entry: model.PlaylistEntry, con: Connection | None = N
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def delete_playlist_entry(entry_id: int, con: Connection | None = None):
|
def delete_playlist_entry(
|
||||||
|
entry_id: int,
|
||||||
|
con: Connection | None = None,
|
||||||
|
):
|
||||||
query = """
|
query = """
|
||||||
DELETE FROM playlists
|
DELETE FROM playlist_entries
|
||||||
WHERE id = :id
|
WHERE id = :id
|
||||||
"""
|
"""
|
||||||
data = dict(id=entry_id)
|
data = dict(id=entry_id)
|
||||||
@@ -47,12 +89,50 @@ def delete_playlist_entry(entry_id: int, con: Connection | None = None):
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def delete_playlist_set(session_id: int, set_id: int, con: Connection | None = None):
|
def delete_playlist_set(
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
con: Connection | None = None,
|
||||||
|
):
|
||||||
query = """
|
query = """
|
||||||
DELETE FROM playlists
|
DELETE FROM playlist_entries
|
||||||
WHERE session_id = :session_id AND set_id = :set_id
|
WHERE playlist_id = :playlist_id AND set_id = :set_id
|
||||||
"""
|
"""
|
||||||
data = dict(session_id=session_id, set_id=set_id)
|
data = dict(playlist_id=playlist_id, set_id=set_id)
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def update_playlist_name(
|
||||||
|
playlist_id: int,
|
||||||
|
name: str | None,
|
||||||
|
con: Connection | None = None,
|
||||||
|
):
|
||||||
|
query = """
|
||||||
|
UPDATE playlists SET
|
||||||
|
name = :name
|
||||||
|
WHERE id = :id
|
||||||
|
"""
|
||||||
|
data = dict(id=playlist_id, name=name)
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def update_playlist_visibility(
|
||||||
|
playlist_id: int,
|
||||||
|
hidden: bool,
|
||||||
|
con: Connection | None = None,
|
||||||
|
):
|
||||||
|
query = """
|
||||||
|
UPDATE playlists SET
|
||||||
|
hidden = :hidden
|
||||||
|
WHERE id = :id
|
||||||
|
"""
|
||||||
|
data = dict(id=playlist_id, hidden=1 if hidden else 0)
|
||||||
with get_connection(con) as con:
|
with get_connection(con) as con:
|
||||||
cur = con.cursor()
|
cur = con.cursor()
|
||||||
_ = cur.execute(query, data)
|
_ = cur.execute(query, data)
|
||||||
|
|||||||
@@ -3,7 +3,17 @@ from typing import TypedDict
|
|||||||
|
|
||||||
from folkugat_web.model import sessions as model
|
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):
|
class SessionRowDict(TypedDict):
|
||||||
@@ -13,6 +23,8 @@ class SessionRowDict(TypedDict):
|
|||||||
end_time: str
|
end_time: str
|
||||||
venue_name: str | None
|
venue_name: str | None
|
||||||
venue_url: str | None
|
venue_url: str | None
|
||||||
|
notes: str | None
|
||||||
|
cartell_url: str | None
|
||||||
is_live: bool
|
is_live: bool
|
||||||
|
|
||||||
|
|
||||||
@@ -24,6 +36,8 @@ def session_to_row(sessio: model.Session) -> SessionRowDict:
|
|||||||
'end_time': sessio.end_time.isoformat(),
|
'end_time': sessio.end_time.isoformat(),
|
||||||
'venue_name': sessio.venue.name,
|
'venue_name': sessio.venue.name,
|
||||||
'venue_url': sessio.venue.url,
|
'venue_url': sessio.venue.url,
|
||||||
|
'notes': sessio.notes,
|
||||||
|
'cartell_url': sessio.cartell_url,
|
||||||
'is_live': sessio.is_live,
|
'is_live': sessio.is_live,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,5 +52,7 @@ def row_to_session(row: SessionRowTuple) -> model.Session:
|
|||||||
name=row[4],
|
name=row[4],
|
||||||
url=row[5],
|
url=row[5],
|
||||||
),
|
),
|
||||||
is_live=row[6],
|
notes=row[6],
|
||||||
|
cartell_url=row[7],
|
||||||
|
is_live=row[8],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from folkugat_web.dal.sql import Connection, get_connection
|
|||||||
def create_db(con: Connection | None = None):
|
def create_db(con: Connection | None = None):
|
||||||
with get_connection(con) as con:
|
with get_connection(con) as con:
|
||||||
create_sessions_table(con)
|
create_sessions_table(con)
|
||||||
|
create_session_playlists_table(con)
|
||||||
|
|
||||||
|
|
||||||
def create_sessions_table(con: Connection):
|
def create_sessions_table(con: Connection):
|
||||||
@@ -15,8 +16,25 @@ def create_sessions_table(con: Connection):
|
|||||||
end_time TEXT NOT NULL,
|
end_time TEXT NOT NULL,
|
||||||
venue_name TEXT,
|
venue_name TEXT,
|
||||||
venue_url TEXT,
|
venue_url TEXT,
|
||||||
|
notes TEXT,
|
||||||
|
cartell_url TEXT,
|
||||||
is_live BOOLEAN DEFAULT false
|
is_live BOOLEAN DEFAULT false
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
cur = con.cursor()
|
cur = con.cursor()
|
||||||
_ = cur.execute(query)
|
_ = cur.execute(query)
|
||||||
|
|
||||||
|
|
||||||
|
def create_session_playlists_table(con: Connection):
|
||||||
|
query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS session_playlists (
|
||||||
|
session_id INTEGER,
|
||||||
|
playlist_type TEXT,
|
||||||
|
playlist_id INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (session_id, playlist_type),
|
||||||
|
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query)
|
||||||
|
|||||||
138
folkugat_web/dal/sql/sessions/playlists.py
Normal file
138
folkugat_web/dal/sql/sessions/playlists.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
from folkugat_web.dal.sql import Connection, get_connection
|
||||||
|
from folkugat_web.dal.sql.temes.conversion import row_to_tema
|
||||||
|
from folkugat_web.model import sessions as sessions_model
|
||||||
|
from folkugat_web.model import temes as temes_model
|
||||||
|
from folkugat_web.model.playlists import PlaylistType
|
||||||
|
from folkugat_web.utils import groupby
|
||||||
|
|
||||||
|
from . import conversion
|
||||||
|
|
||||||
|
|
||||||
|
def get_playlist_id(
|
||||||
|
session_id: int,
|
||||||
|
playlist_type: PlaylistType,
|
||||||
|
con: Connection | None = None,
|
||||||
|
) -> int | None:
|
||||||
|
query = f"""
|
||||||
|
SELECT playlist_id
|
||||||
|
FROM session_playlists
|
||||||
|
WHERE
|
||||||
|
session_id = :session_id AND
|
||||||
|
playlist_type = :playlist_type
|
||||||
|
"""
|
||||||
|
data = dict(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=playlist_type.value,
|
||||||
|
)
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
if row := cur.fetchone():
|
||||||
|
return row[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def insert_playlist(
|
||||||
|
session_id: int,
|
||||||
|
playlist_type: PlaylistType,
|
||||||
|
playlist_id: int,
|
||||||
|
con: Connection | None = None,
|
||||||
|
):
|
||||||
|
query = f"""
|
||||||
|
INSERT INTO session_playlists
|
||||||
|
(session_id, playlist_type, playlist_id)
|
||||||
|
VALUES
|
||||||
|
(:session_id, :playlist_type, :playlist_id)
|
||||||
|
"""
|
||||||
|
data = dict(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=playlist_type.value,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
)
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_playlist(
|
||||||
|
session_id: int,
|
||||||
|
playlist_type: PlaylistType,
|
||||||
|
con: Connection | None = None,
|
||||||
|
):
|
||||||
|
query = f"""
|
||||||
|
DELETE FROM session_playlists
|
||||||
|
WHERE
|
||||||
|
session_id = :session_id AND
|
||||||
|
playlist_type = :playlist_type
|
||||||
|
"""
|
||||||
|
data = dict(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=playlist_type.value,
|
||||||
|
)
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
|
||||||
|
|
||||||
|
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[sessions_model.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.notes, s.cartell_url, s.is_live
|
||||||
|
FROM playlist_entries p
|
||||||
|
JOIN session_playlists sp ON p.playlist_id = sp.playlist_id
|
||||||
|
JOIN sessions s ON sp.session_id = s.id
|
||||||
|
WHERE
|
||||||
|
p.tema_id IN ({placeholders}) AND
|
||||||
|
sp.playlist_type = "{PlaylistType.SESSION_SETLIST.value}"
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, tema_ids)
|
||||||
|
result_rows: list[GetTuneSessionsRow] = cur.fetchall()
|
||||||
|
return dict(groupby(
|
||||||
|
result_rows,
|
||||||
|
key_fn=lambda row: row[0],
|
||||||
|
group_fn=lambda rows: list(conversion.row_to_session(row[1:]) for row in rows)
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
CommonlyPlayedTuneRow = tuple[int, str, str, str, str, int, int]
|
||||||
|
|
||||||
|
|
||||||
|
def get_commonly_played_tunes(
|
||||||
|
tema_id: int,
|
||||||
|
con: Connection | None = None,
|
||||||
|
) -> list[temes_model.CommonlyPlayedTema]:
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
id, title, alternatives, creation_date, modification_date, hidden, count
|
||||||
|
FROM (
|
||||||
|
SELECT tema_id, count(*) count FROM playlist_entries p JOIN (
|
||||||
|
SELECT pe.playlist_id, pe.set_id
|
||||||
|
FROM playlist_entries pe JOIN session_playlists sp USING (playlist_id)
|
||||||
|
WHERE tema_id = :tema_id AND playlist_type = :playlist_type
|
||||||
|
) s
|
||||||
|
ON p.playlist_id == s.playlist_id AND p.set_id == s.set_id
|
||||||
|
WHERE tema_id != :tema_id
|
||||||
|
GROUP BY tema_id
|
||||||
|
) common JOIN temes t ON common.tema_id == t.id
|
||||||
|
"""
|
||||||
|
data = dict(tema_id=tema_id, playlist_type=PlaylistType.SESSION_SETLIST.value)
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
result_rows: list[CommonlyPlayedTuneRow] = cur.fetchall()
|
||||||
|
return [
|
||||||
|
temes_model.CommonlyPlayedTema(
|
||||||
|
tema=row_to_tema(row[:6]),
|
||||||
|
count=row[6],
|
||||||
|
) for row in result_rows
|
||||||
|
]
|
||||||
@@ -82,7 +82,7 @@ def get_sessions(session_id: int | None = None,
|
|||||||
|
|
||||||
clauses_str = " ".join(clauses)
|
clauses_str = " ".join(clauses)
|
||||||
query = f"""
|
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
|
FROM sessions
|
||||||
{clauses_str}
|
{clauses_str}
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ from . import conversion
|
|||||||
def insert_session(session: model.Session, con: Connection | None = None):
|
def insert_session(session: model.Session, con: Connection | None = None):
|
||||||
query = """
|
query = """
|
||||||
INSERT INTO sessions
|
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
|
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 *
|
RETURNING *
|
||||||
"""
|
"""
|
||||||
data = conversion.session_to_row(session)
|
data = conversion.session_to_row(session)
|
||||||
@@ -24,7 +24,8 @@ def update_session(session: model.Session, con: Connection | None = None):
|
|||||||
query = """
|
query = """
|
||||||
UPDATE sessions SET
|
UPDATE sessions SET
|
||||||
date = :date, start_time = :start_time, end_time = :end_time,
|
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
|
WHERE id = :id
|
||||||
"""
|
"""
|
||||||
data = conversion.session_to_row(session)
|
data = conversion.session_to_row(session)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import TypedDict
|
|||||||
from folkugat_web.dal.sql import Connection, get_connection
|
from folkugat_web.dal.sql import Connection, get_connection
|
||||||
from folkugat_web.model import temes as model
|
from folkugat_web.model import temes as model
|
||||||
|
|
||||||
LyricRowTuple = tuple[int, int, str, str]
|
LyricRowTuple = tuple[int, int, str, str, int | None]
|
||||||
|
|
||||||
|
|
||||||
class LyricRowDict(TypedDict):
|
class LyricRowDict(TypedDict):
|
||||||
@@ -12,6 +12,7 @@ class LyricRowDict(TypedDict):
|
|||||||
tema_id: int
|
tema_id: int
|
||||||
title: str
|
title: str
|
||||||
content: str
|
content: str
|
||||||
|
max_columns: int | None
|
||||||
|
|
||||||
|
|
||||||
def lyric_to_row(lyric: model.Lyrics) -> LyricRowDict:
|
def lyric_to_row(lyric: model.Lyrics) -> LyricRowDict:
|
||||||
@@ -20,6 +21,7 @@ def lyric_to_row(lyric: model.Lyrics) -> LyricRowDict:
|
|||||||
"tema_id": lyric.tema_id,
|
"tema_id": lyric.tema_id,
|
||||||
"title": lyric.title,
|
"title": lyric.title,
|
||||||
"content": lyric.content,
|
"content": lyric.content,
|
||||||
|
"max_columns": lyric.max_columns,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ def row_to_lyric(row: LyricRowTuple) -> model.Lyrics:
|
|||||||
tema_id=row[1],
|
tema_id=row[1],
|
||||||
title=row[2],
|
title=row[2],
|
||||||
content=row[3],
|
content=row[3],
|
||||||
|
max_columns=row[4],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@ def get_lyrics(lyric_id: int | None = None, tema_id: int | None = None, con: Con
|
|||||||
|
|
||||||
query = f"""
|
query = f"""
|
||||||
SELECT
|
SELECT
|
||||||
id, tema_id, title, content
|
id, tema_id, title, content, max_columns
|
||||||
FROM tema_lyrics
|
FROM tema_lyrics
|
||||||
{filter_clause}
|
{filter_clause}
|
||||||
"""
|
"""
|
||||||
@@ -77,9 +80,9 @@ def insert_lyric(lyric: model.Lyrics, con: Connection | None = None) -> model.Ly
|
|||||||
data = lyric_to_row(lyric)
|
data = lyric_to_row(lyric)
|
||||||
query = f"""
|
query = f"""
|
||||||
INSERT INTO tema_lyrics
|
INSERT INTO tema_lyrics
|
||||||
(id, tema_id, title, content)
|
(id, tema_id, title, content, max_columns)
|
||||||
VALUES
|
VALUES
|
||||||
(:id, :tema_id, :title, :content)
|
(:id, :tema_id, :title, :content, :max_columns)
|
||||||
RETURNING *
|
RETURNING *
|
||||||
"""
|
"""
|
||||||
with get_connection(con) as con:
|
with get_connection(con) as con:
|
||||||
@@ -95,6 +98,7 @@ def create_lyric(tema_id: int, title: str | None = None, con: Connection | None
|
|||||||
tema_id=tema_id,
|
tema_id=tema_id,
|
||||||
title=title or "",
|
title=title or "",
|
||||||
content="",
|
content="",
|
||||||
|
max_columns=None,
|
||||||
)
|
)
|
||||||
return insert_lyric(new_lyric, con=con)
|
return insert_lyric(new_lyric, con=con)
|
||||||
|
|
||||||
@@ -104,7 +108,7 @@ def update_lyric(lyric: model.Lyrics, con: Connection | None = None):
|
|||||||
query = """
|
query = """
|
||||||
UPDATE tema_lyrics
|
UPDATE tema_lyrics
|
||||||
SET
|
SET
|
||||||
tema_id = :tema_id, title = :title, content = :content
|
tema_id = :tema_id, title = :title, content = :content, max_columns = :max_columns
|
||||||
WHERE
|
WHERE
|
||||||
id = :id
|
id = :id
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from folkugat_web.fragments import llistes
|
||||||
@@ -14,7 +14,7 @@ def sessio_en_directe(request: Request):
|
|||||||
raise RuntimeError("Got a session without id!")
|
raise RuntimeError("Got a session without id!")
|
||||||
|
|
||||||
current_set = None
|
current_set = None
|
||||||
if playlist := playlists_service.get_playlist(session_id=session.id):
|
if playlist := service.get_session_setlist(session_id=session.id):
|
||||||
if playlist.sets:
|
if playlist.sets:
|
||||||
current_set = playlists_service.add_temes_to_set(playlist.sets[-1])
|
current_set = playlists_service.add_temes_to_set(playlist.sets[-1])
|
||||||
|
|
||||||
|
|||||||
42
folkugat_web/fragments/llistes.py
Normal file
42
folkugat_web/fragments/llistes.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from fastapi import Request
|
||||||
|
from folkugat_web.model import playlists as playlists_model
|
||||||
|
from folkugat_web.model.pagines import Pages
|
||||||
|
from folkugat_web.services import playlists as playlists_service
|
||||||
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
|
|
||||||
|
def llistes_pagina(request: Request, logged_in: bool):
|
||||||
|
playlists = playlists_service.get_all_playlists(logged_in=logged_in)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llistes/pagina.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"playlists": playlists,
|
||||||
|
"Pages": Pages,
|
||||||
|
"menu_selected_id": Pages.Llistes,
|
||||||
|
"playlist_list_id": "playlist-list",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def llistes_editor_insert_row(request: Request):
|
||||||
|
new_playlist = playlists_service.create_playlist(name=playlists_model.DEFAULT_PLAYLIST_NAME)
|
||||||
|
return llistes_editor_row(request, new_playlist)
|
||||||
|
|
||||||
|
|
||||||
|
def llistes_editor_row(request: Request, playlist: playlists_model.Playlist):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llistes/playlist_entry.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"playlist": playlist,
|
||||||
|
"logged_in": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def llistes_editor_delete_row(playlist_id: int):
|
||||||
|
playlists_service.delete_playlist(playlist_id)
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
return HTMLResponse()
|
||||||
237
folkugat_web/fragments/playlist.py
Normal file
237
folkugat_web/fragments/playlist.py
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
from fastapi import Request
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from folkugat_web.model.pagines import Pages
|
||||||
|
from folkugat_web.model.playlists import PlaylistType
|
||||||
|
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 add_set(
|
||||||
|
request: Request,
|
||||||
|
playlist_id: int,
|
||||||
|
logged_in: bool,
|
||||||
|
):
|
||||||
|
new_set = playlists_service.add_set(playlist_id=playlist_id)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llista/set_entry.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"new_entry": True,
|
||||||
|
"playlist_id": playlist_id,
|
||||||
|
"set_id": new_set.id,
|
||||||
|
"set_entry": new_set,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_set(request: Request, playlist_id: int, set_id: int, logged_in: bool):
|
||||||
|
set_entry = playlists_service.get_set(playlist_id=playlist_id, set_id=set_id)
|
||||||
|
if set_entry:
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llista/set_entry.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"new_entry": True,
|
||||||
|
"playlist_id": playlist_id,
|
||||||
|
"set_id": set_id,
|
||||||
|
"set_entry": set_entry,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return HTMLResponse()
|
||||||
|
|
||||||
|
|
||||||
|
def delete_set(playlist_id: int, set_id: int):
|
||||||
|
playlists_service.delete_set(playlist_id=playlist_id, set_id=set_id)
|
||||||
|
return HTMLResponse()
|
||||||
|
|
||||||
|
|
||||||
|
def add_tema(request: Request, playlist_id: int, set_id: int, logged_in: bool):
|
||||||
|
new_tema = playlists_service.add_tema(playlist_id=playlist_id, set_id=set_id)
|
||||||
|
new_tema = playlists_service.add_tema_to_tema_in_set(new_tema)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llista/tema_editor.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"playlist_id": playlist_id,
|
||||||
|
"set_id": set_id,
|
||||||
|
"tema_entry": new_tema,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_tema(request: Request, playlist_id: int, set_id: int, entry_id: int, logged_in: bool):
|
||||||
|
tema_entry = playlists_service.get_tema(entry_id=entry_id)
|
||||||
|
tema_entry = playlists_service.add_tema_to_tema_in_set(tema_entry)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llista/tema_entry.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"playlist_id": playlist_id,
|
||||||
|
"set_id": set_id,
|
||||||
|
"tema_entry": tema_entry,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_tema_editor(request: Request, playlist_id: int, set_id: int, entry_id: int, logged_in: bool):
|
||||||
|
tema_entry = playlists_service.get_tema(entry_id=entry_id)
|
||||||
|
tema_entry = playlists_service.add_tema_to_tema_in_set(tema_entry)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llista/tema_editor.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"playlist_id": playlist_id,
|
||||||
|
"set_id": set_id,
|
||||||
|
"tema_entry": tema_entry,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_tema(playlist_id: int, set_id: int, entry_id: int):
|
||||||
|
playlists_service.delete_tema(entry_id=entry_id)
|
||||||
|
if not playlists_service.get_set(playlist_id=playlist_id, set_id=set_id):
|
||||||
|
headers = {
|
||||||
|
"HX-Trigger": f"reload-set-{set_id}"
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
headers = {}
|
||||||
|
return HTMLResponse(headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
def busca_tema(
|
||||||
|
request: Request,
|
||||||
|
playlist_id: int,
|
||||||
|
set_id: int,
|
||||||
|
entry_id: int,
|
||||||
|
query: str,
|
||||||
|
):
|
||||||
|
n_results = 4
|
||||||
|
suggestions = []
|
||||||
|
if not query:
|
||||||
|
# If there is no query, suggest tunes commonly played together
|
||||||
|
set_entry = playlists_service.get_set(
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
set_id=set_id,
|
||||||
|
)
|
||||||
|
if set_entry:
|
||||||
|
tema_ids = {tema_in_set.tema_id
|
||||||
|
for tema_in_set in set_entry.temes
|
||||||
|
if tema_in_set and tema_in_set.tema_id is not None}
|
||||||
|
commonly_played_tema_ids = {
|
||||||
|
cpt.tema.id
|
||||||
|
for tema_id in tema_ids
|
||||||
|
for cpt in sessions_service.get_commonly_played_temes(tema_id)
|
||||||
|
if cpt.tema.id is not None
|
||||||
|
} - tema_ids
|
||||||
|
suggestions = query_service.get_temes_by_ids(
|
||||||
|
tema_ids=list(commonly_played_tema_ids)
|
||||||
|
)
|
||||||
|
if len(suggestions) >= n_results:
|
||||||
|
suggestions = suggestions[:n_results]
|
||||||
|
elif not suggestions:
|
||||||
|
suggestions = search_service.busca_temes(
|
||||||
|
query=query,
|
||||||
|
properties=[],
|
||||||
|
hidden=True,
|
||||||
|
limit=n_results,
|
||||||
|
offset=0,
|
||||||
|
)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llista/tema_results.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"playlist_id": playlist_id,
|
||||||
|
"set_id": set_id,
|
||||||
|
"entry_id": entry_id,
|
||||||
|
"results": suggestions,
|
||||||
|
"query": query,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_tema(request: Request, logged_in: bool, playlist_id: int, set_id: int, entry_id: int, tema_id: int | None):
|
||||||
|
playlists_service.set_tema(playlist_id=playlist_id, set_id=set_id, entry_id=entry_id, tema_id=tema_id)
|
||||||
|
tema_entry = playlists_service.get_tema(entry_id=entry_id)
|
||||||
|
tema_entry = playlists_service.add_tema_to_tema_in_set(tema_entry)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llista/tema_entry.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"playlist_id": playlist_id,
|
||||||
|
"set_id": set_id,
|
||||||
|
"tema_entry": tema_entry,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def pagina(request: Request, playlist_id: int, logged_in: bool):
|
||||||
|
playlist = playlists_service.get_playlist(playlist_id=playlist_id)
|
||||||
|
if not playlist:
|
||||||
|
from fastapi import HTTPException
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find playlist")
|
||||||
|
playlist = playlists_service.add_temes_to_playlist(playlist)
|
||||||
|
playlist = await playlists_service.add_playlist_score_to_playlist(playlist)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llista/pagina.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"playlist_id": playlist_id,
|
||||||
|
"playlist": playlist,
|
||||||
|
"Pages": Pages,
|
||||||
|
"PlaylistType": PlaylistType
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def name(request: Request, playlist_id: int, logged_in: bool):
|
||||||
|
playlist = playlists_service.get_playlist(playlist_id=playlist_id)
|
||||||
|
if not playlist:
|
||||||
|
from fastapi import HTTPException
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find playlist")
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llista/name.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"playlist_id": playlist_id,
|
||||||
|
"playlist": playlist,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def name_editor(request: Request, playlist_id: int, logged_in: bool):
|
||||||
|
playlist = playlists_service.get_playlist(playlist_id=playlist_id)
|
||||||
|
if not playlist:
|
||||||
|
from fastapi import HTTPException
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find playlist")
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llista/editor/name.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"playlist_id": playlist_id,
|
||||||
|
"playlist": playlist,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def visibility(request: Request, logged_in: bool, playlist):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/llista/visibility.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"playlist": playlist,
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
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 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(
|
|
||||||
"fragments/sessio/set_entry.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"new_entry": True,
|
|
||||||
"session_id": session_id,
|
|
||||||
"set_id": new_set.id,
|
|
||||||
"set_entry": new_set,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_set(request: Request, session_id: int, set_id: int, logged_in: bool):
|
|
||||||
set_entry = playlists_service.get_set(session_id=session_id, set_id=set_id)
|
|
||||||
if set_entry:
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/sessio/set_entry.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"new_entry": True,
|
|
||||||
"session_id": session_id,
|
|
||||||
"set_id": set_id,
|
|
||||||
"set_entry": set_entry,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return HTMLResponse()
|
|
||||||
|
|
||||||
|
|
||||||
def delete_set(session_id: int, set_id: int):
|
|
||||||
playlists_service.delete_set(session_id=session_id, set_id=set_id)
|
|
||||||
return HTMLResponse()
|
|
||||||
|
|
||||||
|
|
||||||
def add_tema(request: Request, session_id: int, set_id: int, logged_in: bool):
|
|
||||||
new_tema = playlists_service.add_tema(session_id=session_id, set_id=set_id)
|
|
||||||
playlists_service.add_tema_to_tema_in_set(new_tema)
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/sessio/tema_editor.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"session_id": session_id,
|
|
||||||
"set_id": set_id,
|
|
||||||
"tema_entry": new_tema,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_tema(request: Request, session_id: int, set_id: int, entry_id: int, logged_in: bool):
|
|
||||||
tema_entry = playlists_service.get_tema(entry_id=entry_id)
|
|
||||||
playlists_service.add_tema_to_tema_in_set(tema_entry)
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/sessio/tema_entry.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"session_id": session_id,
|
|
||||||
"set_id": set_id,
|
|
||||||
"tema_entry": tema_entry,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_tema_editor(request: Request, session_id: int, set_id: int, entry_id: int, logged_in: bool):
|
|
||||||
tema_entry = playlists_service.get_tema(entry_id=entry_id)
|
|
||||||
playlists_service.add_tema_to_tema_in_set(tema_entry)
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/sessio/tema_editor.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"session_id": session_id,
|
|
||||||
"set_id": set_id,
|
|
||||||
"tema_entry": tema_entry,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def delete_tema(session_id: int, set_id: int, entry_id: int):
|
|
||||||
playlists_service.delete_tema(entry_id=entry_id)
|
|
||||||
if not playlists_service.get_set(session_id=session_id, set_id=set_id):
|
|
||||||
headers = {
|
|
||||||
"HX-Trigger": f"reload-set-{set_id}"
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
headers = {}
|
|
||||||
return HTMLResponse(headers=headers)
|
|
||||||
|
|
||||||
|
|
||||||
def busca_tema(
|
|
||||||
request: Request,
|
|
||||||
session_id: int,
|
|
||||||
set_id: int,
|
|
||||||
entry_id: int,
|
|
||||||
query: str,
|
|
||||||
):
|
|
||||||
n_results = 4
|
|
||||||
suggestions = []
|
|
||||||
if not query:
|
|
||||||
# If there is no query, suggest tunes commonly played together
|
|
||||||
set_entry = playlists_service.get_set(
|
|
||||||
session_id=session_id,
|
|
||||||
set_id=set_id,
|
|
||||||
)
|
|
||||||
if set_entry:
|
|
||||||
tema_ids = {tema_in_set.tema_id
|
|
||||||
for tema_in_set in set_entry.temes
|
|
||||||
if tema_in_set and tema_in_set.tema_id is not None}
|
|
||||||
commonly_played_tema_ids = {
|
|
||||||
cpt.tema.id
|
|
||||||
for tema_id in tema_ids
|
|
||||||
for cpt in playlists_service.get_commonly_played_temes(tema_id)
|
|
||||||
if cpt.tema.id is not None
|
|
||||||
} - tema_ids
|
|
||||||
suggestions = query_service.get_temes_by_ids(
|
|
||||||
tema_ids=list(commonly_played_tema_ids)
|
|
||||||
)
|
|
||||||
if len(suggestions) >= n_results:
|
|
||||||
suggestions = suggestions[:n_results]
|
|
||||||
elif not suggestions:
|
|
||||||
suggestions = search_service.busca_temes(
|
|
||||||
query=query,
|
|
||||||
properties=[],
|
|
||||||
hidden=True,
|
|
||||||
limit=n_results,
|
|
||||||
offset=0,
|
|
||||||
)
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/sessio/tema_results.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"session_id": session_id,
|
|
||||||
"set_id": set_id,
|
|
||||||
"entry_id": entry_id,
|
|
||||||
"results": suggestions,
|
|
||||||
"query": query,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def set_tema(request: Request, logged_in: bool, session_id: int, set_id: int, entry_id: int, tema_id: int | None):
|
|
||||||
playlists_service.set_tema(session_id=session_id, set_id=set_id, entry_id=entry_id, tema_id=tema_id)
|
|
||||||
tema_entry = playlists_service.get_tema(entry_id=entry_id)
|
|
||||||
playlists_service.add_tema_to_tema_in_set(tema_entry)
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/sessio/tema_entry.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"session_id": session_id,
|
|
||||||
"set_id": set_id,
|
|
||||||
"tema_entry": tema_entry,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
0
folkugat_web/fragments/sessio/__init__.py
Normal file
0
folkugat_web/fragments/sessio/__init__.py
Normal file
26
folkugat_web/fragments/sessio/cartell.py
Normal file
26
folkugat_web/fragments/sessio/cartell.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from fastapi import Request
|
||||||
|
from folkugat_web.services import sessions as sessions_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,
|
||||||
|
}
|
||||||
|
)
|
||||||
30
folkugat_web/fragments/sessio/notes.py
Normal file
30
folkugat_web/fragments/sessio/notes.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from fastapi import Request
|
||||||
|
from folkugat_web.services import sessions as sessions_service
|
||||||
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
|
|
||||||
|
def notes(request: Request, session_id: int, logged_in: bool):
|
||||||
|
session = sessions_service.get_session(session_id=session_id)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/sessio/notes.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"session_id": session_id,
|
||||||
|
"session": session,
|
||||||
|
"notes": session.notes if session and session.notes else "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def notes_editor(request: Request, session_id: int):
|
||||||
|
session = sessions_service.get_session(session_id=session_id)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/sessio/notes_editor.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"session_id": session_id,
|
||||||
|
"notes": session.notes if session and session.notes else "",
|
||||||
|
"max": max,
|
||||||
|
}
|
||||||
|
)
|
||||||
22
folkugat_web/fragments/sessio/page.py
Normal file
22
folkugat_web/fragments/sessio/page.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from fastapi import HTTPException, Request
|
||||||
|
from folkugat_web.model.pagines import Pages
|
||||||
|
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")
|
||||||
|
session = sessions_service.add_playlists_to_session(session=session)
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/sessio/pagina.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"Pages": Pages,
|
||||||
|
"session_id": session_id,
|
||||||
|
"session": session,
|
||||||
|
"date_names": sessions_service.get_date_names,
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -6,22 +6,20 @@ from folkugat_web.services import sessions as sessions_service
|
|||||||
from folkugat_web.templates import templates
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
|
|
||||||
async def pagina(request: Request, session_id: int, set_id: int, logged_in: bool):
|
async def pagina(request: Request, playlist_id: int, set_id: int, logged_in: bool):
|
||||||
session = sessions_service.get_session(session_id=session_id)
|
set_ = playlists_service.get_set(playlist_id=playlist_id, set_id=set_id)
|
||||||
set_ = playlists_service.get_set(session_id=session_id, set_id=set_id)
|
|
||||||
if not set_:
|
if not set_:
|
||||||
raise HTTPException(status_code=404, detail="Set not found")
|
raise HTTPException(status_code=404, detail="Set not found")
|
||||||
set_ = playlists_service.add_temes_to_set(set_)
|
set_ = playlists_service.add_temes_to_set(set_)
|
||||||
set_ = await playlists_service.add_set_score_to_set(set_)
|
set_ = await playlists_service.add_set_score_to_set(set_)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"fragments/sessio/set/pagina.html",
|
"fragments/llista/set/pagina.html",
|
||||||
{
|
{
|
||||||
"request": request,
|
"request": request,
|
||||||
"logged_in": logged_in,
|
"logged_in": logged_in,
|
||||||
"Pages": Pages,
|
"Pages": Pages,
|
||||||
"session_id": session_id,
|
|
||||||
"session": session,
|
|
||||||
"set": set_,
|
"set": set_,
|
||||||
|
"session": None,
|
||||||
"date_names": sessions_service.get_date_names,
|
"date_names": sessions_service.get_date_names,
|
||||||
"LinkType": LinkType,
|
"LinkType": LinkType,
|
||||||
"ContentType": ContentType,
|
"ContentType": ContentType,
|
||||||
@@ -33,12 +31,12 @@ async def live(request: Request, logged_in: bool):
|
|||||||
session = sessions_service.get_live_session()
|
session = sessions_service.get_live_session()
|
||||||
set_ = None
|
set_ = None
|
||||||
if session and session.id:
|
if session and session.id:
|
||||||
playlist = playlists_service.get_playlist(session_id=session.id)
|
playlist = sessions_service.get_session_setlist(session_id=session.id)
|
||||||
if playlist.sets:
|
if playlist and playlist.sets:
|
||||||
set_ = playlists_service.add_temes_to_set(playlist.sets[-1])
|
set_ = playlists_service.add_temes_to_set(playlist.sets[-1])
|
||||||
set_ = await playlists_service.add_set_score_to_set(set_)
|
set_ = await playlists_service.add_set_score_to_set(set_)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"fragments/sessio/set/pagina.html",
|
"fragments/llista/set/pagina.html",
|
||||||
{
|
{
|
||||||
"request": request,
|
"request": request,
|
||||||
"logged_in": logged_in,
|
"logged_in": logged_in,
|
||||||
@@ -56,12 +54,12 @@ async def live_set(request: Request, logged_in: bool):
|
|||||||
session = sessions_service.get_live_session()
|
session = sessions_service.get_live_session()
|
||||||
set_ = None
|
set_ = None
|
||||||
if session and session.id:
|
if session and session.id:
|
||||||
playlist = playlists_service.get_playlist(session_id=session.id)
|
playlist = sessions_service.get_session_setlist(session_id=session.id)
|
||||||
if playlist.sets:
|
if playlist and playlist.sets:
|
||||||
set_ = playlists_service.add_temes_to_set(playlist.sets[-1])
|
set_ = playlists_service.add_temes_to_set(playlist.sets[-1])
|
||||||
set_ = await playlists_service.add_set_score_to_set(set_)
|
set_ = await playlists_service.add_set_score_to_set(set_)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"fragments/sessio/set/set_page.html",
|
"fragments/llista/set/set_page.html",
|
||||||
{
|
{
|
||||||
"request": request,
|
"request": request,
|
||||||
"logged_in": logged_in,
|
"logged_in": logged_in,
|
||||||
|
|||||||
@@ -32,13 +32,16 @@ class LyricsText:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_lyrics(cls, lyrics: temes.Lyrics, max_cols: int = 3) -> Self:
|
def from_lyrics(cls, lyrics: temes.Lyrics, max_cols: int = 3) -> Self:
|
||||||
|
# Use stored max_columns if available, otherwise use the provided max_cols
|
||||||
|
effective_max_cols = lyrics.max_columns if lyrics.max_columns is not None else max_cols
|
||||||
|
# Remove ~ characters before processing
|
||||||
paragraphs = [
|
paragraphs = [
|
||||||
LyricsParagraph(lines=par_str.splitlines())
|
LyricsParagraph(lines=[line.replace("~", "") for line in par_str.splitlines()])
|
||||||
for par_str in lyrics.content.split("\n\n") if par_str
|
for par_str in lyrics.content.split("\n\n") if par_str
|
||||||
]
|
]
|
||||||
return cls(lines=[
|
return cls(lines=[
|
||||||
LyricsLine(paragraphs=list(line_pars))
|
LyricsLine(paragraphs=list(line_pars))
|
||||||
for line_pars in batched(paragraphs, max_cols)
|
for line_pars in batched(paragraphs, effective_max_cols)
|
||||||
])
|
])
|
||||||
|
|
||||||
def hash(self) -> bytes:
|
def hash(self) -> bytes:
|
||||||
@@ -89,3 +92,15 @@ class LilypondSet:
|
|||||||
self.title.encode(),
|
self.title.encode(),
|
||||||
*(tune.hash() for tune in self.tunes),
|
*(tune.hash() for tune in self.tunes),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class LilypondPlaylist:
|
||||||
|
title: str
|
||||||
|
sets: list[LilypondSet]
|
||||||
|
|
||||||
|
def hash(self) -> bytes:
|
||||||
|
return get_hash(
|
||||||
|
self.title.encode(),
|
||||||
|
*(lilypond_set.hash() for lilypond_set in self.sets),
|
||||||
|
)
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ import enum
|
|||||||
class Pages(enum.IntEnum):
|
class Pages(enum.IntEnum):
|
||||||
Sessions = 0
|
Sessions = 0
|
||||||
Temes = 1
|
Temes = 1
|
||||||
|
Llistes = 2
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
|
import enum
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
|
||||||
from folkugat_web.model.temes import Tema
|
from folkugat_web.model.temes import Tema
|
||||||
from folkugat_web.utils import groupby
|
from folkugat_web.utils import groupby
|
||||||
|
|
||||||
|
DEFAULT_PLAYLIST_NAME = "Llista de temes"
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistType(enum.Enum):
|
||||||
|
SESSION_SETLIST = "session_setlist"
|
||||||
|
SESSION_SLOWJAM = "session_slowjam"
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class PlaylistEntry:
|
class PlaylistEntry:
|
||||||
id: int | None
|
id: int | None
|
||||||
session_id: int
|
playlist_id: int
|
||||||
set_id: int
|
set_id: int
|
||||||
tema_id: int | None
|
tema_id: int | None
|
||||||
|
|
||||||
@@ -20,17 +28,21 @@ class TemaInSet:
|
|||||||
tema_id: int | None
|
tema_id: int | None
|
||||||
tema: Tema | None
|
tema: Tema | None
|
||||||
|
|
||||||
def to_playlist_entry(self, session_id: int, set_id: int) -> PlaylistEntry:
|
def to_playlist_entry(self, playlist_id: int, set_id: int) -> PlaylistEntry:
|
||||||
return PlaylistEntry(
|
return PlaylistEntry(
|
||||||
id=self.id,
|
id=self.id,
|
||||||
session_id=session_id,
|
playlist_id=playlist_id,
|
||||||
set_id=set_id,
|
set_id=set_id,
|
||||||
tema_id=self.tema_id,
|
tema_id=self.tema_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_playlist_entry(cls, entry: PlaylistEntry) -> Self:
|
def from_playlist_entry(cls, entry: PlaylistEntry) -> Self:
|
||||||
return cls(id=entry.id, tema_id=entry.tema_id, tema=None)
|
return cls(
|
||||||
|
id=entry.id,
|
||||||
|
tema_id=entry.tema_id,
|
||||||
|
tema=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
@@ -39,16 +51,22 @@ class SetScore:
|
|||||||
pdf_url: str | None
|
pdf_url: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class PlaylistScore:
|
||||||
|
img_url: str | None
|
||||||
|
pdf_url: str | None
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Set:
|
class Set:
|
||||||
id: int
|
id: int
|
||||||
temes: list[TemaInSet]
|
temes: list[TemaInSet]
|
||||||
score: SetScore | None
|
score: SetScore | None
|
||||||
|
|
||||||
def to_playlist_entries(self, session_id: int) -> Iterator[PlaylistEntry]:
|
def to_playlist_entries(self, playlist_id: int) -> Iterator[PlaylistEntry]:
|
||||||
for tema_in_set in self.temes:
|
for tema_in_set in self.temes:
|
||||||
yield tema_in_set.to_playlist_entry(
|
yield tema_in_set.to_playlist_entry(
|
||||||
session_id=session_id,
|
playlist_id=playlist_id,
|
||||||
set_id=self.id,
|
set_id=self.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,26 +76,33 @@ class Set:
|
|||||||
raise ValueError("All PlaylistEntries must have the same session_id")
|
raise ValueError("All PlaylistEntries must have the same session_id")
|
||||||
return cls(
|
return cls(
|
||||||
id=set_id,
|
id=set_id,
|
||||||
temes=[TemaInSet.from_playlist_entry(entry) for entry in sorted(entries, key=lambda e: e.id or 0)],
|
temes=[
|
||||||
|
TemaInSet.from_playlist_entry(entry)
|
||||||
|
for entry in sorted(entries, key=lambda e: e.id or 0)
|
||||||
|
],
|
||||||
score=None,
|
score=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Playlist:
|
class Playlist:
|
||||||
session_id: int
|
id: int
|
||||||
|
name: str | None
|
||||||
sets: list[Set]
|
sets: list[Set]
|
||||||
|
hidden: bool = True
|
||||||
|
playlist_score: PlaylistScore | None = None
|
||||||
|
|
||||||
def to_playlist_entries(self) -> Iterator[PlaylistEntry]:
|
def to_playlist_entries(self) -> Iterator[PlaylistEntry]:
|
||||||
for set_entry in self.sets:
|
for set_entry in self.sets:
|
||||||
yield from set_entry.to_playlist_entries(session_id=self.session_id)
|
yield from set_entry.to_playlist_entries(playlist_id=self.id)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_playlist_entries(cls, session_id: int, entries: list[PlaylistEntry]) -> Self:
|
def from_playlist_entries(cls, playlist_id: int, name: str | None, entries: list[PlaylistEntry]) -> Self:
|
||||||
if any(entry.session_id != session_id for entry in entries):
|
if any(entry.playlist_id != playlist_id for entry in entries):
|
||||||
raise ValueError("All PlaylistEntries must have the same session_id")
|
raise ValueError("All PlaylistEntries must have the same playlist_id")
|
||||||
return cls(
|
return cls(
|
||||||
session_id=session_id,
|
id=playlist_id,
|
||||||
|
name=name,
|
||||||
sets=[
|
sets=[
|
||||||
Set.from_playlist_entries(set_id, set_entries)
|
Set.from_playlist_entries(set_id, set_entries)
|
||||||
for set_id, set_entries in groupby(entries, key_fn=lambda e: e.set_id, group_fn=list)
|
for set_id, set_entries in groupby(entries, key_fn=lambda e: e.set_id, group_fn=list)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import dataclasses
|
|||||||
import datetime
|
import datetime
|
||||||
import enum
|
import enum
|
||||||
|
|
||||||
|
from folkugat_web.model import playlists
|
||||||
|
|
||||||
DEFAULT_START_TIME = datetime.time(20, 30)
|
DEFAULT_START_TIME = datetime.time(20, 30)
|
||||||
DEFAULT_END_TIME = datetime.time(22, 30)
|
DEFAULT_END_TIME = datetime.time(22, 30)
|
||||||
|
|
||||||
@@ -19,6 +21,10 @@ class Session:
|
|||||||
start_time: datetime.time = DEFAULT_START_TIME
|
start_time: datetime.time = DEFAULT_START_TIME
|
||||||
end_time: datetime.time = DEFAULT_END_TIME
|
end_time: datetime.time = DEFAULT_END_TIME
|
||||||
venue: SessionVenue = dataclasses.field(default_factory=SessionVenue)
|
venue: SessionVenue = dataclasses.field(default_factory=SessionVenue)
|
||||||
|
notes: str | None = None
|
||||||
|
cartell_url: str | None = None
|
||||||
|
slowjam: playlists.Playlist | None = None
|
||||||
|
setlist: playlists.Playlist | None = None
|
||||||
is_live: bool = False
|
is_live: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ import dataclasses
|
|||||||
import datetime
|
import datetime
|
||||||
import enum
|
import enum
|
||||||
import itertools
|
import itertools
|
||||||
from typing import Self
|
from typing import TYPE_CHECKING, Self
|
||||||
|
|
||||||
from folkugat_web.model.lilypond.processing import RenderError
|
from folkugat_web.model.lilypond.processing import RenderError
|
||||||
from folkugat_web.model.search import NGrams
|
from folkugat_web.model.search import NGrams
|
||||||
from folkugat_web.model.sessions import Session
|
|
||||||
from folkugat_web.services import ngrams
|
from folkugat_web.services import ngrams
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from folkugat_web.model.sessions import Session
|
||||||
|
|
||||||
|
|
||||||
class ContentType(enum.Enum):
|
class ContentType(enum.Enum):
|
||||||
PARTITURA = "partitura"
|
PARTITURA = "partitura"
|
||||||
@@ -58,6 +60,7 @@ class Lyrics:
|
|||||||
tema_id: int
|
tema_id: int
|
||||||
title: str
|
title: str
|
||||||
content: str
|
content: str
|
||||||
|
max_columns: int | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import mimetypes
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterable, Iterator
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ import aiofiles
|
|||||||
import magic
|
import magic
|
||||||
from fastapi import HTTPException, UploadFile
|
from fastapi import HTTPException, UploadFile
|
||||||
from folkugat_web.config import db
|
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 links as links_dal
|
||||||
from folkugat_web.dal.sql.temes import scores as scores_dal
|
from folkugat_web.dal.sql.temes import scores as scores_dal
|
||||||
from folkugat_web.log import logger
|
from folkugat_web.log import logger
|
||||||
@@ -22,22 +23,16 @@ async def get_mimetype(upload_file: UploadFile) -> str:
|
|||||||
return info.mime_type
|
return info.mime_type
|
||||||
|
|
||||||
|
|
||||||
ACCEPTED_MIMETYPES = [
|
IMAGE_MIMETYPE = re.compile(r"image/.+")
|
||||||
re.compile(r"image/.+"),
|
PDF_MIMETYPE = re.compile(r".+/pdf")
|
||||||
re.compile(r".+/pdf"),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def check_mimetype(mimetype: str) -> None:
|
def check_mimetype(mimetype: str, accepted_mimetypes: Iterable[re.Pattern[str]]) -> None:
|
||||||
if not any(regex.match(mimetype) for regex in ACCEPTED_MIMETYPES):
|
if not any(regex.match(mimetype) for regex in accepted_mimetypes):
|
||||||
raise HTTPException(status_code=400, detail=f"Unsupported file type: {mimetype}")
|
raise HTTPException(status_code=400, detail=f"Unsupported file type: {mimetype}")
|
||||||
|
|
||||||
|
|
||||||
def get_db_file_path(filepath: Path) -> str:
|
def check_upload_file_size(upload_file: UploadFile) -> None:
|
||||||
return f"{db.DB_FILES_URL}/{filepath.relative_to(db.DB_FILES_DIR)}"
|
|
||||||
|
|
||||||
|
|
||||||
async def store_file(tema_id: int, upload_file: UploadFile) -> str:
|
|
||||||
if not upload_file.size:
|
if not upload_file.size:
|
||||||
raise HTTPException(status_code=400, detail="Couldn't find out the size of the file")
|
raise HTTPException(status_code=400, detail="Couldn't find out the size of the file")
|
||||||
if upload_file.size > db.FILE_MAX_SIZE:
|
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)",
|
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)
|
mimetype = await get_mimetype(upload_file)
|
||||||
check_mimetype(mimetype)
|
check_mimetype(mimetype, [IMAGE_MIMETYPE, PDF_MIMETYPE])
|
||||||
|
|
||||||
extension = mimetypes.guess_extension(mimetype) or ""
|
extension = mimetypes.guess_extension(mimetype) or ""
|
||||||
filepath = create_tema_filename(tema_id=tema_id, extension=extension)
|
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)
|
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:
|
def create_tema_filename(tema_id: int, extension: str = "") -> Path:
|
||||||
filename = str(uuid.uuid4().hex) + extension
|
filename = str(uuid.uuid4().hex) + extension
|
||||||
filedir = db.DB_FILES_TEMA_DIR / str(tema_id)
|
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
|
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:
|
def create_tmp_filename(extension: str = "") -> Path:
|
||||||
filename = str(uuid.uuid4().hex) + extension
|
filename = str(uuid.uuid4().hex) + extension
|
||||||
filepath = db.DB_FILES_TMP_DIR / filename
|
filepath = db.DB_FILES_TMP_DIR / filename
|
||||||
@@ -76,6 +100,10 @@ def get_set_filename(filename: str) -> Path:
|
|||||||
return db.DB_FILES_SET_DIR / filename
|
return db.DB_FILES_SET_DIR / filename
|
||||||
|
|
||||||
|
|
||||||
|
def get_playlist_filename(filename: str) -> Path:
|
||||||
|
return db.DB_FILES_PLAYLIST_DIR / filename
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def tmp_file(content: str):
|
async def tmp_file(content: str):
|
||||||
input_filename = create_tmp_filename(extension=".ly")
|
input_filename = create_tmp_filename(extension=".ly")
|
||||||
@@ -94,11 +122,13 @@ def list_files(tema_id: str) -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
def get_orphan_files() -> Iterator[Path]:
|
def get_orphan_files() -> Iterator[Path]:
|
||||||
link_urls = {link.url for link in links_dal.get_links()}
|
alive_urls = (
|
||||||
score_pdf_urls = {score.pdf_url for score in scores_dal.get_scores() if score.pdf_url is not None}
|
{link.url for link in links_dal.get_links()}
|
||||||
score_img_urls = {score.img_url for score in scores_dal.get_scores() if score.img_url is not None}
|
| {score.pdf_url for score in scores_dal.get_scores() if score.pdf_url is not None}
|
||||||
score_preview_urls = {score.preview_url for score in scores_dal.get_scores() if score.preview_url is not None}
|
| {score.img_url for score in scores_dal.get_scores() if score.img_url is not None}
|
||||||
alive_urls = link_urls | score_pdf_urls | score_img_urls | score_preview_urls
|
| {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(
|
return filter(
|
||||||
lambda p: p.is_file() and get_db_file_path(p) not in alive_urls,
|
lambda p: p.is_file() and get_db_file_path(p) not in alive_urls,
|
||||||
itertools.chain(
|
itertools.chain(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from fastapi import HTTPException
|
|||||||
from folkugat_web.model import playlists as playlists_model
|
from folkugat_web.model import playlists as playlists_model
|
||||||
from folkugat_web.model import temes as model
|
from folkugat_web.model import temes as model
|
||||||
from folkugat_web.model.lilypond import score as lilypond_model
|
from folkugat_web.model.lilypond import score as lilypond_model
|
||||||
|
from folkugat_web.services import playlists as playlists_service
|
||||||
from folkugat_web.services.temes import lyrics as lyrics_service
|
from folkugat_web.services.temes import lyrics as lyrics_service
|
||||||
from folkugat_web.services.temes import properties as properties_service
|
from folkugat_web.services.temes import properties as properties_service
|
||||||
from folkugat_web.services.temes import query as temes_q
|
from folkugat_web.services.temes import query as temes_q
|
||||||
@@ -96,3 +97,20 @@ def set_from_set(set_entry: playlists_model.Set) -> lilypond_model.LilypondSet:
|
|||||||
title=set_title,
|
title=set_title,
|
||||||
tunes=tunes
|
tunes=tunes
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def playlist_from_playlist(playlist: playlists_model.Playlist) -> lilypond_model.LilypondPlaylist:
|
||||||
|
"""
|
||||||
|
The playlist is assumed to be enriched with tunes
|
||||||
|
"""
|
||||||
|
lilypond_sets = []
|
||||||
|
for set_entry in playlist.sets:
|
||||||
|
lilypond_set = set_from_set(set_entry)
|
||||||
|
if lilypond_set.tunes and all(map(playlists_service._elegible_for_set_score, lilypond_set.tunes)):
|
||||||
|
lilypond_sets.append(lilypond_set)
|
||||||
|
|
||||||
|
playlist_title = playlist.name or "Llista"
|
||||||
|
return lilypond_model.LilypondPlaylist(
|
||||||
|
title=playlist_title,
|
||||||
|
sets=lilypond_sets
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from folkugat_web.model.lilypond.score import LilypondSet, LilypondTune
|
from folkugat_web.model.lilypond.score import LilypondPlaylist, LilypondSet, LilypondTune
|
||||||
from folkugat_web.templates import templates
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
SCORE_BEGINNING = "% --- SCORE BEGINNING --- %"
|
SCORE_BEGINNING = "% --- SCORE BEGINNING --- %"
|
||||||
@@ -23,3 +23,10 @@ def set_source(tune_set: LilypondSet) -> str:
|
|||||||
score_beginning=SCORE_BEGINNING,
|
score_beginning=SCORE_BEGINNING,
|
||||||
tune_set=tune_set,
|
tune_set=tune_set,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def playlist_source(playlist: LilypondPlaylist) -> str:
|
||||||
|
return templates.get_template("lilypond/playlist.ly").render(
|
||||||
|
score_beginning=SCORE_BEGINNING,
|
||||||
|
playlist=playlist,
|
||||||
|
)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from folkugat_web.dal.sql._connection import get_connection
|
|||||||
from folkugat_web.dal.sql.playlists import query, write
|
from folkugat_web.dal.sql.playlists import query, write
|
||||||
from folkugat_web.log import logger
|
from folkugat_web.log import logger
|
||||||
from folkugat_web.model import playlists
|
from folkugat_web.model import playlists
|
||||||
from folkugat_web.model import temes as temes_model
|
|
||||||
from folkugat_web.model.lilypond import score as lilypond_model
|
from folkugat_web.model.lilypond import score as lilypond_model
|
||||||
from folkugat_web.services import files as files_service
|
from folkugat_web.services import files as files_service
|
||||||
from folkugat_web.services.lilypond import build as lilypond_build
|
from folkugat_web.services.lilypond import build as lilypond_build
|
||||||
@@ -41,37 +40,45 @@ def add_tema_to_tema_in_set(tema_in_set: playlists.TemaInSet) -> playlists.TemaI
|
|||||||
return tema_in_set
|
return tema_in_set
|
||||||
|
|
||||||
|
|
||||||
def get_playlist(session_id: int, con: Connection | None = None) -> playlists.Playlist:
|
def get_playlist(playlist_id: int, con: Connection | None = None) -> playlists.Playlist:
|
||||||
|
with get_connection(con) as playlist_con:
|
||||||
|
playlist_name = query.get_playlist_name(playlist_id=playlist_id, con=playlist_con)
|
||||||
return playlists.Playlist.from_playlist_entries(
|
return playlists.Playlist.from_playlist_entries(
|
||||||
session_id=session_id,
|
playlist_id=playlist_id,
|
||||||
entries=list(query.get_playlist_entries(session_id=session_id, con=con))
|
name=playlist_name,
|
||||||
|
entries=list(query.get_playlist_entries(playlist_id=playlist_id, con=playlist_con))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_set(session_id: int, con: Connection | None = None) -> playlists.Set:
|
def update_name(playlist_id: int, name: str | None) -> playlists.Playlist:
|
||||||
|
write.update_playlist_name(playlist_id=playlist_id, name=name)
|
||||||
|
return get_playlist(playlist_id=playlist_id)
|
||||||
|
|
||||||
|
|
||||||
|
def add_set(playlist_id: int, con: Connection | None = None) -> playlists.Set:
|
||||||
with get_connection(con) as con:
|
with get_connection(con) as con:
|
||||||
curr_playlist = get_playlist(session_id=session_id, con=con)
|
curr_playlist = get_playlist(playlist_id=playlist_id, con=con)
|
||||||
new_set_id = max([set_entry.id for set_entry in curr_playlist.sets], default=0) + 1
|
new_set_id = max([set_entry.id for set_entry in curr_playlist.sets], default=0) + 1
|
||||||
new_entry = playlists.PlaylistEntry(id=None, session_id=session_id, set_id=new_set_id, tema_id=None)
|
new_entry = playlists.PlaylistEntry(id=None, playlist_id=playlist_id, set_id=new_set_id, tema_id=None)
|
||||||
inserted_entry = write.insert_playlist_entry(new_entry)
|
inserted_entry = write.insert_playlist_entry(new_entry)
|
||||||
return playlists.Set.from_playlist_entries(set_id=inserted_entry.set_id, entries=[inserted_entry])
|
return playlists.Set.from_playlist_entries(set_id=inserted_entry.set_id, entries=[inserted_entry])
|
||||||
|
|
||||||
|
|
||||||
def get_set(session_id: int, set_id: int, con: Connection | None = None) -> playlists.Set | None:
|
def get_set(playlist_id: int, set_id: int, con: Connection | None = None) -> playlists.Set | None:
|
||||||
entries = list(query.get_playlist_entries(session_id=session_id, set_id=set_id, con=con))
|
entries = list(query.get_playlist_entries(playlist_id=playlist_id, set_id=set_id, con=con))
|
||||||
if entries:
|
if entries:
|
||||||
return playlists.Set.from_playlist_entries(set_id=set_id, entries=entries)
|
return playlists.Set.from_playlist_entries(set_id=set_id, entries=entries)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def delete_set(session_id: int, set_id: int, con: Connection | None = None):
|
def delete_set(playlist_id: int, set_id: int, con: Connection | None = None):
|
||||||
write.delete_playlist_set(session_id=session_id, set_id=set_id, con=con)
|
write.delete_playlist_set(playlist_id=playlist_id, set_id=set_id, con=con)
|
||||||
|
|
||||||
|
|
||||||
def add_tema(session_id: int, set_id: int, con: Connection | None = None) -> playlists.TemaInSet:
|
def add_tema(playlist_id: int, set_id: int, con: Connection | None = None) -> playlists.TemaInSet:
|
||||||
with get_connection(con) as con:
|
with get_connection(con) as con:
|
||||||
new_entry = playlists.PlaylistEntry(id=None, session_id=session_id, set_id=set_id, tema_id=None)
|
new_entry = playlists.PlaylistEntry(id=None, playlist_id=playlist_id, set_id=set_id, tema_id=None)
|
||||||
inserted_entry = write.insert_playlist_entry(new_entry)
|
inserted_entry = write.insert_playlist_entry(new_entry)
|
||||||
return playlists.TemaInSet.from_playlist_entry(inserted_entry)
|
return playlists.TemaInSet.from_playlist_entry(inserted_entry)
|
||||||
|
|
||||||
@@ -87,10 +94,10 @@ def delete_tema(entry_id: int, con: Connection | None = None):
|
|||||||
write.delete_playlist_entry(entry_id=entry_id, con=con)
|
write.delete_playlist_entry(entry_id=entry_id, con=con)
|
||||||
|
|
||||||
|
|
||||||
def set_tema(session_id: int, set_id: int, entry_id: int, tema_id: int | None,
|
def set_tema(playlist_id: int, set_id: int, entry_id: int, tema_id: int | None,
|
||||||
con: Connection | None = None):
|
con: Connection | None = None):
|
||||||
with get_connection(con) as con:
|
with get_connection(con) as con:
|
||||||
new_entry = playlists.PlaylistEntry(id=entry_id, session_id=session_id, set_id=set_id, tema_id=tema_id)
|
new_entry = playlists.PlaylistEntry(id=entry_id, playlist_id=playlist_id, set_id=set_id, tema_id=tema_id)
|
||||||
write.update_playlist_entry(entry=new_entry, con=con)
|
write.update_playlist_entry(entry=new_entry, con=con)
|
||||||
|
|
||||||
|
|
||||||
@@ -165,7 +172,73 @@ async def add_set_score_to_set(tune_set: playlists.Set) -> playlists.Set:
|
|||||||
return tune_set
|
return tune_set
|
||||||
|
|
||||||
|
|
||||||
def get_commonly_played_temes(
|
async def get_or_create_playlist_score(playlist: playlists.Playlist) -> playlists.PlaylistScore | None:
|
||||||
tema_id: int,
|
# The playlist is assumed to be enriched with tunes
|
||||||
) -> list[temes_model.CommonlyPlayedTema]:
|
if not playlist.sets:
|
||||||
return query.get_commonly_played_tunes(tema_id=tema_id)
|
return None
|
||||||
|
|
||||||
|
lilypond_playlist = lilypond_build.playlist_from_playlist(playlist)
|
||||||
|
if not lilypond_playlist.sets:
|
||||||
|
return None
|
||||||
|
|
||||||
|
playlist_score_hash = lilypond_playlist.hash().hex()
|
||||||
|
|
||||||
|
pdf_filepath = files_service.get_playlist_filename(f"{playlist_score_hash}.pdf")
|
||||||
|
|
||||||
|
if not pdf_filepath.exists():
|
||||||
|
# No score exists, so we need to create it
|
||||||
|
playlist_source = lilypond_source.playlist_source(playlist=lilypond_playlist)
|
||||||
|
out_filepath = files_service.get_playlist_filename(playlist_score_hash)
|
||||||
|
async with files_service.tmp_file(content=playlist_source) as source_filepath:
|
||||||
|
if not pdf_filepath.exists():
|
||||||
|
pdf_result = await lilypond_render.render_file(
|
||||||
|
input_file=source_filepath,
|
||||||
|
output=lilypond_render.RenderOutput.PDF,
|
||||||
|
output_file=out_filepath,
|
||||||
|
)
|
||||||
|
if pdf_result.error is not None:
|
||||||
|
return None
|
||||||
|
if pdf_result.result is None:
|
||||||
|
raise RuntimeError("This shouldn't happen")
|
||||||
|
pdf_filepath = pdf_result.result
|
||||||
|
|
||||||
|
return playlists.PlaylistScore(
|
||||||
|
img_url=None, # Only PDF generation for now
|
||||||
|
pdf_url=files_service.get_db_file_path(pdf_filepath),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def add_playlist_score_to_playlist(playlist: playlists.Playlist) -> playlists.Playlist:
|
||||||
|
if score := await get_or_create_playlist_score(playlist=playlist):
|
||||||
|
return dataclasses.replace(
|
||||||
|
playlist,
|
||||||
|
playlist_score=score,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return playlist
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_playlists(logged_in: bool = False) -> list[playlists.Playlist]:
|
||||||
|
return list(query.get_all_playlists(logged_in=logged_in))
|
||||||
|
|
||||||
|
|
||||||
|
def set_visibility(playlist_id: int, hidden: bool) -> playlists.Playlist:
|
||||||
|
with get_connection() as con:
|
||||||
|
playlist = get_playlist(playlist_id=playlist_id, con=con)
|
||||||
|
if playlist is None:
|
||||||
|
raise ValueError(f"No playlist found with playlist_id = {playlist_id}!")
|
||||||
|
|
||||||
|
# Update hidden status in database
|
||||||
|
write.update_playlist_visibility(playlist_id=playlist_id, hidden=hidden, con=con)
|
||||||
|
|
||||||
|
# Return updated playlist
|
||||||
|
return dataclasses.replace(playlist, hidden=hidden)
|
||||||
|
|
||||||
|
|
||||||
|
def create_playlist(name: str | None = None) -> playlists.Playlist:
|
||||||
|
playlist_id = write.create_playlist(name=name)
|
||||||
|
return get_playlist(playlist_id=playlist_id)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_playlist(playlist_id: int):
|
||||||
|
write.delete_playlist(playlist_id=playlist_id)
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
import dataclasses
|
||||||
import datetime
|
import datetime
|
||||||
from datetime import date as Date
|
from datetime import date as Date
|
||||||
|
|
||||||
from folkugat_web.config import date as config
|
from folkugat_web.config import date as config
|
||||||
|
from folkugat_web.dal.sql import Connection, get_connection
|
||||||
|
from folkugat_web.dal.sql.playlists import write as playlists_write
|
||||||
|
from folkugat_web.dal.sql.sessions import playlists as session_playlists
|
||||||
from folkugat_web.dal.sql.sessions import query, write
|
from folkugat_web.dal.sql.sessions import query, write
|
||||||
from folkugat_web.model import sessions as model
|
from folkugat_web.model import sessions as model
|
||||||
|
from folkugat_web.model import temes as temes_model
|
||||||
|
from folkugat_web.model.playlists import Playlist, PlaylistType
|
||||||
from folkugat_web.model.sql import Order, OrderCol, Range
|
from folkugat_web.model.sql import Order, OrderCol, Range
|
||||||
|
from folkugat_web.services import playlists as playlists_service
|
||||||
|
|
||||||
|
|
||||||
def get_date_names(date: Date) -> model.DateNames:
|
def get_date_names(date: Date) -> model.DateNames:
|
||||||
@@ -60,14 +67,19 @@ def get_session(session_id: int) -> model.Session | None:
|
|||||||
|
|
||||||
|
|
||||||
def insert_session(session: model.Session):
|
def insert_session(session: model.Session):
|
||||||
return write.insert_session(session)
|
created_session = write.insert_session(session)
|
||||||
|
if created_session.id is not None:
|
||||||
|
_create_session_playlists(created_session.id)
|
||||||
|
return created_session
|
||||||
|
|
||||||
|
|
||||||
def set_session(session: model.Session):
|
def set_session(session: model.Session):
|
||||||
write.update_session(session)
|
write.update_session(session)
|
||||||
|
_update_session_playlist_names(session)
|
||||||
|
|
||||||
|
|
||||||
def delete_session(session_id: int):
|
def delete_session(session_id: int):
|
||||||
|
_delete_session_playlists(session_id)
|
||||||
write.delete_session_by_id(session_id)
|
write.delete_session_by_id(session_id)
|
||||||
|
|
||||||
|
|
||||||
@@ -77,3 +89,181 @@ def stop_live_sessions():
|
|||||||
|
|
||||||
def set_live_session(session_id: int):
|
def set_live_session(session_id: int):
|
||||||
write.set_live_session(session_id=session_id)
|
write.set_live_session(session_id=session_id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_session_playlist_id(
|
||||||
|
session_id: int,
|
||||||
|
playlist_type: PlaylistType,
|
||||||
|
con: Connection | None = None,
|
||||||
|
) -> int | None:
|
||||||
|
with get_connection(con=con) as con:
|
||||||
|
return session_playlists.get_playlist_id(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=playlist_type,
|
||||||
|
con=con,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_session_playlist(
|
||||||
|
session_id: int,
|
||||||
|
playlist_type: PlaylistType,
|
||||||
|
con: Connection | None = None,
|
||||||
|
) -> Playlist | None:
|
||||||
|
with get_connection(con=con) as con:
|
||||||
|
playlist_id = get_session_playlist_id(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=playlist_type,
|
||||||
|
con=con,
|
||||||
|
)
|
||||||
|
if playlist_id is None:
|
||||||
|
return None
|
||||||
|
return playlists_service.get_playlist(
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
con=con,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_session_setlist(
|
||||||
|
session_id: int,
|
||||||
|
con: Connection | None = None,
|
||||||
|
) -> Playlist | None:
|
||||||
|
return get_session_playlist(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=PlaylistType.SESSION_SETLIST,
|
||||||
|
con=con,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_session_slowjam(
|
||||||
|
session_id: int,
|
||||||
|
con: Connection | None = None,
|
||||||
|
) -> Playlist | None:
|
||||||
|
return get_session_playlist(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=PlaylistType.SESSION_SLOWJAM,
|
||||||
|
con=con,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_playlists_to_session(session: model.Session) -> model.Session:
|
||||||
|
if session.id is not None:
|
||||||
|
with get_connection() as con:
|
||||||
|
setlist = get_session_setlist(session_id=session.id, con=con)
|
||||||
|
if setlist:
|
||||||
|
setlist = playlists_service.add_temes_to_playlist(setlist)
|
||||||
|
slowjam = get_session_slowjam(session_id=session.id, con=con)
|
||||||
|
if slowjam:
|
||||||
|
slowjam = playlists_service.add_temes_to_playlist(slowjam)
|
||||||
|
session = dataclasses.replace(
|
||||||
|
session,
|
||||||
|
setlist=setlist,
|
||||||
|
slowjam=slowjam,
|
||||||
|
)
|
||||||
|
return session
|
||||||
|
|
||||||
|
|
||||||
|
def get_commonly_played_temes(
|
||||||
|
tema_id: int,
|
||||||
|
) -> list[temes_model.CommonlyPlayedTema]:
|
||||||
|
return session_playlists.get_commonly_played_tunes(tema_id=tema_id)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_playlist_names(session: model.Session) -> tuple[str, str]:
|
||||||
|
date_names = get_date_names(session.date)
|
||||||
|
|
||||||
|
setlist_name = f"Sessió del {date_names.day} {date_names.month_name} de {date_names.year}"
|
||||||
|
slowjam_name = f"Slow Jam del {date_names.day} {date_names.month_name} de {date_names.year}"
|
||||||
|
|
||||||
|
return setlist_name, slowjam_name
|
||||||
|
|
||||||
|
|
||||||
|
def _create_session_playlists(session_id: int):
|
||||||
|
session = get_session(session_id=session_id)
|
||||||
|
if not session:
|
||||||
|
return
|
||||||
|
|
||||||
|
setlist_name, slowjam_name = _get_playlist_names(session=session)
|
||||||
|
|
||||||
|
setlist_playlist_id = playlists_write.create_playlist(name=setlist_name)
|
||||||
|
slowjam_playlist_id = playlists_write.create_playlist(name=slowjam_name)
|
||||||
|
|
||||||
|
with get_connection() as con:
|
||||||
|
session_playlists.insert_playlist(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=PlaylistType.SESSION_SETLIST,
|
||||||
|
playlist_id=setlist_playlist_id,
|
||||||
|
con=con
|
||||||
|
)
|
||||||
|
session_playlists.insert_playlist(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=PlaylistType.SESSION_SLOWJAM,
|
||||||
|
playlist_id=slowjam_playlist_id,
|
||||||
|
con=con
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _update_session_playlist_names(session: model.Session):
|
||||||
|
if session.id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
setlist_name, slowjam_name = _get_playlist_names(session=session)
|
||||||
|
|
||||||
|
with get_connection() as con:
|
||||||
|
setlist_playlist_id = session_playlists.get_playlist_id(
|
||||||
|
session_id=session.id,
|
||||||
|
playlist_type=PlaylistType.SESSION_SETLIST,
|
||||||
|
con=con
|
||||||
|
)
|
||||||
|
slowjam_playlist_id = session_playlists.get_playlist_id(
|
||||||
|
session_id=session.id,
|
||||||
|
playlist_type=PlaylistType.SESSION_SLOWJAM,
|
||||||
|
con=con
|
||||||
|
)
|
||||||
|
|
||||||
|
if setlist_playlist_id is not None:
|
||||||
|
playlists_write.update_playlist_name(
|
||||||
|
playlist_id=setlist_playlist_id,
|
||||||
|
name=setlist_name,
|
||||||
|
con=con
|
||||||
|
)
|
||||||
|
if slowjam_playlist_id is not None:
|
||||||
|
playlists_write.update_playlist_name(
|
||||||
|
playlist_id=slowjam_playlist_id,
|
||||||
|
name=slowjam_name,
|
||||||
|
con=con
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_session_playlists(session_id: int):
|
||||||
|
with get_connection() as con:
|
||||||
|
setlist_playlist_id = session_playlists.get_playlist_id(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=PlaylistType.SESSION_SETLIST,
|
||||||
|
con=con
|
||||||
|
)
|
||||||
|
slowjam_playlist_id = session_playlists.get_playlist_id(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=PlaylistType.SESSION_SLOWJAM,
|
||||||
|
con=con
|
||||||
|
)
|
||||||
|
|
||||||
|
if setlist_playlist_id is not None:
|
||||||
|
session_playlists.delete_playlist(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=PlaylistType.SESSION_SETLIST,
|
||||||
|
con=con,
|
||||||
|
)
|
||||||
|
playlists_write.delete_playlist(
|
||||||
|
playlist_id=setlist_playlist_id,
|
||||||
|
con=con,
|
||||||
|
)
|
||||||
|
if slowjam_playlist_id is not None:
|
||||||
|
session_playlists.delete_playlist(
|
||||||
|
session_id=session_id,
|
||||||
|
playlist_type=PlaylistType.SESSION_SLOWJAM,
|
||||||
|
con=con,
|
||||||
|
)
|
||||||
|
playlists_write.delete_playlist(
|
||||||
|
playlist_id=slowjam_playlist_id,
|
||||||
|
con=con,
|
||||||
|
)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ def _sub(pattern: str, sub: str) -> Callable[[str], str]:
|
|||||||
def _clean_string(lyrics_str: str) -> str:
|
def _clean_string(lyrics_str: str) -> str:
|
||||||
return (
|
return (
|
||||||
FnChain.transform(lyrics_str) |
|
FnChain.transform(lyrics_str) |
|
||||||
|
# _sub(r"~", "") |
|
||||||
_sub(r"\t", " ") |
|
_sub(r"\t", " ") |
|
||||||
_sub(r" +", " ") |
|
_sub(r" +", " ") |
|
||||||
_sub(r" \n", "\n") |
|
_sub(r" \n", "\n") |
|
||||||
@@ -39,8 +40,13 @@ def get_lyric_by_id(lyric_id: int, tema_id: int | None = None) -> model.Lyrics |
|
|||||||
return next(iter(lyrics_dal.get_lyrics(lyric_id=lyric_id, tema_id=tema_id)), None)
|
return next(iter(lyrics_dal.get_lyrics(lyric_id=lyric_id, tema_id=tema_id)), None)
|
||||||
|
|
||||||
|
|
||||||
def update_lyric(lyric: model.Lyrics, title: str, content: str):
|
def update_lyric(lyric: model.Lyrics, title: str, content: str, max_columns: int | None = None):
|
||||||
lyric = dataclasses.replace(lyric, title=title.strip(), content=_clean_string(content))
|
# Validate max_columns if provided
|
||||||
|
if max_columns is not None:
|
||||||
|
if not isinstance(max_columns, int) or max_columns < 1 or max_columns > 10:
|
||||||
|
raise ValueError("max_columns must be an integer between 1 and 10")
|
||||||
|
|
||||||
|
lyric = dataclasses.replace(lyric, title=title.strip(), content=_clean_string(content), max_columns=max_columns)
|
||||||
lyrics_dal.update_lyric(lyric=lyric)
|
lyrics_dal.update_lyric(lyric=lyric)
|
||||||
return lyric
|
return lyric
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
|
|
||||||
from folkugat_web.dal.sql.playlists import query as playlists_q
|
from folkugat_web.dal.sql.sessions import playlists as session_playlists
|
||||||
from folkugat_web.dal.sql.temes import query as temes_q
|
from folkugat_web.dal.sql.temes import query as temes_q
|
||||||
from folkugat_web.model import sessions as sessions_model
|
from folkugat_web.model import sessions as sessions_model
|
||||||
from folkugat_web.model import temes as model
|
from folkugat_web.model import temes as model
|
||||||
@@ -20,7 +20,7 @@ def tema_compute_stats(
|
|||||||
) -> model.Tema:
|
) -> model.Tema:
|
||||||
if tema.id:
|
if tema.id:
|
||||||
if tune_sessions_dict is None:
|
if tune_sessions_dict is None:
|
||||||
tune_sessions_dict = playlists_q.get_tune_sessions(tema_ids=[tema.id])
|
tune_sessions_dict = session_playlists.get_tune_sessions(tema_ids=[tema.id])
|
||||||
if tema.id and (tune_sessions := tune_sessions_dict.get(tema.id)):
|
if tema.id and (tune_sessions := tune_sessions_dict.get(tema.id)):
|
||||||
unique_tune_sessions = set(tune_sessions)
|
unique_tune_sessions = set(tune_sessions)
|
||||||
tema.stats = model.Stats(
|
tema.stats = model.Stats(
|
||||||
@@ -33,7 +33,7 @@ def tema_compute_stats(
|
|||||||
def temes_compute_stats(temes: Iterable[model.Tema]) -> list[model.Tema]:
|
def temes_compute_stats(temes: Iterable[model.Tema]) -> list[model.Tema]:
|
||||||
temes = list(temes)
|
temes = list(temes)
|
||||||
tema_ids = [tema.id for tema in temes if tema.id is not None]
|
tema_ids = [tema.id for tema in temes if tema.id is not None]
|
||||||
tune_sessions_dict = playlists_q.get_tune_sessions(tema_ids=tema_ids)
|
tune_sessions_dict = session_playlists.get_tune_sessions(tema_ids=tema_ids)
|
||||||
return [tema_compute_stats(tema=tema, tune_sessions_dict=tune_sessions_dict) for tema in temes]
|
return [tema_compute_stats(tema=tema, tune_sessions_dict=tune_sessions_dict) for tema in temes]
|
||||||
|
|
||||||
|
|
||||||
@@ -41,5 +41,5 @@ def tema_compute_played_with(
|
|||||||
tema: model.Tema,
|
tema: model.Tema,
|
||||||
) -> model.Tema:
|
) -> model.Tema:
|
||||||
if tema.id:
|
if tema.id:
|
||||||
tema.played_with = playlists_q.get_commonly_played_tunes(tema_id=tema.id)
|
tema.played_with = session_playlists.get_commonly_played_tunes(tema_id=tema.id)
|
||||||
return tema
|
return tema
|
||||||
|
|||||||
10
scripts/04_add_session_notes_i_cartell.py
Normal file
10
scripts/04_add_session_notes_i_cartell.py
Normal 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!")
|
||||||
57
scripts/05_migrate_playlists.py
Normal file
57
scripts/05_migrate_playlists.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
from folkugat_web.dal.sql import get_connection
|
||||||
|
from folkugat_web.dal.sql.playlists import write as playlists_w
|
||||||
|
from folkugat_web.dal.sql.playlists.ddl import (create_playlist_entries_table,
|
||||||
|
create_playlists_table)
|
||||||
|
from folkugat_web.dal.sql.sessions import playlists as session_playlists
|
||||||
|
from folkugat_web.dal.sql.sessions import query as sessions_q
|
||||||
|
from folkugat_web.dal.sql.sessions.ddl import create_session_playlists_table
|
||||||
|
from folkugat_web.model.playlists import PlaylistType
|
||||||
|
|
||||||
|
with get_connection() as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
|
||||||
|
drop_query = """DROP TABLE rehearse_data"""
|
||||||
|
_ = cur.execute(drop_query)
|
||||||
|
|
||||||
|
alter_query = """ALTER TABLE playlists RENAME TO playlists_old"""
|
||||||
|
_ = cur.execute(alter_query)
|
||||||
|
|
||||||
|
# Create new tables
|
||||||
|
create_playlist_entries_table(con=con)
|
||||||
|
create_playlists_table(con=con)
|
||||||
|
create_session_playlists_table(con=con)
|
||||||
|
|
||||||
|
# Create new playlists
|
||||||
|
sessions = sessions_q.get_sessions(con=con)
|
||||||
|
for session in sessions:
|
||||||
|
if session.id is None:
|
||||||
|
raise ValueError("Session without id!")
|
||||||
|
|
||||||
|
setlist_id = playlists_w.create_playlist(con=con)
|
||||||
|
session_playlists.insert_playlist(
|
||||||
|
session_id=session.id,
|
||||||
|
playlist_type=PlaylistType.SESSION_SETLIST,
|
||||||
|
playlist_id=setlist_id,
|
||||||
|
con=con,
|
||||||
|
)
|
||||||
|
|
||||||
|
slowjam_id = playlists_w.create_playlist(con=con)
|
||||||
|
session_playlists.insert_playlist(
|
||||||
|
session_id=session.id,
|
||||||
|
playlist_type=PlaylistType.SESSION_SLOWJAM,
|
||||||
|
playlist_id=slowjam_id,
|
||||||
|
con=con,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Migrate
|
||||||
|
migrate_query = """
|
||||||
|
INSERT INTO playlist_entries
|
||||||
|
(id, playlist_id, set_id, tema_id)
|
||||||
|
SELECT
|
||||||
|
NULL, sp.playlist_id, pl_old.set_id, pl_old.tema_id
|
||||||
|
FROM playlists_old pl_old JOIN session_playlists sp USING (session_id)
|
||||||
|
WHERE sp.playlist_type = "session_setlist"
|
||||||
|
"""
|
||||||
|
_ = cur.execute(migrate_query)
|
||||||
|
|
||||||
|
print("DONE!")
|
||||||
71
scripts/06_migrate_session_playlist_names.py
Executable file
71
scripts/06_migrate_session_playlist_names.py
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
from collections.abc import Iterable
|
||||||
|
|
||||||
|
from folkugat_web.dal.sql import get_connection
|
||||||
|
from folkugat_web.dal.sql.sessions import playlists as session_playlists
|
||||||
|
from folkugat_web.model.playlists import PlaylistType
|
||||||
|
from folkugat_web.services import sessions as sessions_service
|
||||||
|
from folkugat_web.dal.sql.playlists import write as playlists_write
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main migration function."""
|
||||||
|
with get_connection() as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
query = """ SELECT
|
||||||
|
sp.playlist_id,
|
||||||
|
sp.session_id,
|
||||||
|
s.date,
|
||||||
|
sp.playlist_type,
|
||||||
|
p.name as current_name
|
||||||
|
FROM session_playlists sp
|
||||||
|
JOIN sessions s ON sp.session_id = s.id
|
||||||
|
JOIN playlists p ON sp.playlist_id = p.id
|
||||||
|
WHERE p.name IS NULL OR p.name = ''
|
||||||
|
ORDER BY sp.session_id, sp.playlist_type """
|
||||||
|
_ = cur.execute(query)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
print("No session playlists need renaming.")
|
||||||
|
return
|
||||||
|
|
||||||
|
updated_count = 0
|
||||||
|
for row in rows:
|
||||||
|
playlist_id, session_id, session_date, playlist_type, current_name = row
|
||||||
|
|
||||||
|
# Parse session date properly
|
||||||
|
session_date = sessions_service.get_date_names(
|
||||||
|
datetime.date.fromisoformat(session_date)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate proper name based on playlist type
|
||||||
|
if playlist_type == PlaylistType.SESSION_SETLIST.value:
|
||||||
|
new_name = f"Sessió del {session_date.day} de {session_date.month_name} de {session_date.year}"
|
||||||
|
elif playlist_type == PlaylistType.SESSION_SLOWJAM.value:
|
||||||
|
new_name = f"Slow Jam del {session_date.day} de {session_date.month_name} de {session_date.year}"
|
||||||
|
else:
|
||||||
|
print(f"Unknown playlist type {playlist_type} for playlist {playlist_id}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Update the playlist name using existing function
|
||||||
|
playlists_write.update_playlist_name(
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
name=new_name,
|
||||||
|
con=con
|
||||||
|
)
|
||||||
|
|
||||||
|
current_name_display = current_name or "None"
|
||||||
|
print(
|
||||||
|
f"Updated playlist {playlist_id} (session {session_id}) "
|
||||||
|
f"from '{current_name_display}' to '{new_name}'"
|
||||||
|
)
|
||||||
|
updated_count += 1
|
||||||
|
|
||||||
|
con.commit()
|
||||||
|
print(f"Migration completed! Updated {updated_count} playlist names.")
|
||||||
|
print("DONE!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
46
scripts/07_add_playlist_hidden.py
Normal file
46
scripts/07_add_playlist_hidden.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Migration script to add hidden column to playlists table and set all existing playlists to hidden.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Add the project root to the path so we can import project modules
|
||||||
|
project_root = Path(__file__).parent
|
||||||
|
sys.path.insert(0, str(project_root))
|
||||||
|
|
||||||
|
from folkugat_web.config.db import DB_FILE
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print(f"Connecting to database at: {DB_FILE}")
|
||||||
|
|
||||||
|
with sqlite3.connect(DB_FILE) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
|
||||||
|
# Check if hidden column already exists
|
||||||
|
cur.execute("PRAGMA table_info(playlists)")
|
||||||
|
columns = [column[1] for column in cur.fetchall()]
|
||||||
|
|
||||||
|
if 'hidden' not in columns:
|
||||||
|
print("Adding hidden column to playlists table...")
|
||||||
|
cur.execute("ALTER TABLE playlists ADD COLUMN hidden INTEGER NOT NULL DEFAULT 1")
|
||||||
|
print("Column added successfully.")
|
||||||
|
else:
|
||||||
|
print("Hidden column already exists.")
|
||||||
|
|
||||||
|
# Set all existing playlists to hidden (1)
|
||||||
|
print("Setting all existing playlists to hidden...")
|
||||||
|
cur.execute("UPDATE playlists SET hidden = 1")
|
||||||
|
updated_count = cur.rowcount
|
||||||
|
print(f"Updated {updated_count} playlists to hidden.")
|
||||||
|
|
||||||
|
con.commit()
|
||||||
|
|
||||||
|
print("Migration completed successfully!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
8
scripts/08_add_lyrics_max_columns.py
Normal file
8
scripts/08_add_lyrics_max_columns.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from folkugat_web.dal.sql import get_connection
|
||||||
|
|
||||||
|
with get_connection() as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
alter_query = """ ALTER TABLE tema_lyrics ADD COLUMN max_columns INTEGER"""
|
||||||
|
_ = cur.execute(alter_query)
|
||||||
|
|
||||||
|
print("DONE!")
|
||||||
Reference in New Issue
Block a user