Migrated links, lyrics and properties
This commit is contained in:
@@ -59,6 +59,7 @@
|
|||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
export PYTHONPATH=${pythonEnv}/${python.sitePackages}:$PYTHONPATH
|
export PYTHONPATH=${pythonEnv}/${python.sitePackages}:$PYTHONPATH
|
||||||
|
export PYTHONPATH=$(pwd):$PYTHONPATH
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
from . import editor, index
|
from . import editor, index, links, lyrics, properties
|
||||||
|
|||||||
@@ -11,78 +11,3 @@ def title_editor(
|
|||||||
tema_id: int,
|
tema_id: int,
|
||||||
):
|
):
|
||||||
return tema.title_editor(request=request, logged_in=logged_in, tema_id=tema_id)
|
return tema.title_editor(request=request, logged_in=logged_in, tema_id=tema_id)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/tema/{tema_id}/editor/lyric/{lyric_id}")
|
|
||||||
def lyric_editor(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.RequireLogin,
|
|
||||||
tema_id: int,
|
|
||||||
lyric_id: int,
|
|
||||||
):
|
|
||||||
return tema.lyric_editor(
|
|
||||||
request=request,
|
|
||||||
logged_in=logged_in,
|
|
||||||
tema_id=tema_id,
|
|
||||||
lyric_id=lyric_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/tema/{tema_id}/editor/link/{link_id}")
|
|
||||||
def link_editor(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.RequireLogin,
|
|
||||||
tema_id: int,
|
|
||||||
link_id: int,
|
|
||||||
):
|
|
||||||
return tema.link_editor(
|
|
||||||
request=request,
|
|
||||||
logged_in=logged_in,
|
|
||||||
tema_id=tema_id,
|
|
||||||
link_id=link_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/tema/{tema_id}/editor/link/{link_id}/url")
|
|
||||||
def link_editor_url_input(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.RequireLogin,
|
|
||||||
tema_id: int,
|
|
||||||
link_id: int,
|
|
||||||
):
|
|
||||||
return tema.link_editor_url(
|
|
||||||
request=request,
|
|
||||||
logged_in=logged_in,
|
|
||||||
tema_id=tema_id,
|
|
||||||
link_id=link_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/tema/{tema_id}/editor/link/{link_id}/file")
|
|
||||||
def link_editor_file_input(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.RequireLogin,
|
|
||||||
tema_id: int,
|
|
||||||
link_id: int,
|
|
||||||
):
|
|
||||||
return tema.link_editor_file(
|
|
||||||
request=request,
|
|
||||||
logged_in=logged_in,
|
|
||||||
tema_id=tema_id,
|
|
||||||
link_id=link_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/tema/{tema_id}/editor/property/{property_id}")
|
|
||||||
def property_editor(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.RequireLogin,
|
|
||||||
tema_id: int,
|
|
||||||
property_id: int,
|
|
||||||
):
|
|
||||||
return tema.property_editor(
|
|
||||||
request=request,
|
|
||||||
logged_in=logged_in,
|
|
||||||
tema_id=tema_id,
|
|
||||||
property_id=property_id,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import Request, UploadFile
|
from fastapi import HTTPException, Request
|
||||||
from fastapi.params import File, Form, Param
|
from fastapi.params import Form
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from folkugat_web.api import router
|
from folkugat_web.api import router
|
||||||
from folkugat_web.fragments import tema, temes
|
from folkugat_web.fragments import tema, temes
|
||||||
from folkugat_web.model import temes as model
|
from folkugat_web.services import auth
|
||||||
from folkugat_web.services import auth, files
|
from folkugat_web.services.temes import links as links_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 query as temes_q
|
||||||
from folkugat_web.services.temes import write as temes_w
|
from folkugat_web.services.temes import write as temes_w
|
||||||
from folkugat_web.services.temes.links import guess_link_type
|
|
||||||
from folkugat_web.templates import templates
|
from folkugat_web.templates import templates
|
||||||
|
from folkugat_web.utils import FnChain
|
||||||
|
|
||||||
|
|
||||||
@router.get("/tema/{tema_id}")
|
@router.get("/tema/{tema_id}")
|
||||||
@@ -27,7 +30,17 @@ def page(request: Request, logged_in: auth.LoggedIn, tema_id: int):
|
|||||||
|
|
||||||
@router.get("/api/tema/{tema_id}")
|
@router.get("/api/tema/{tema_id}")
|
||||||
def contingut(request: Request, logged_in: auth.LoggedIn, tema_id: int):
|
def contingut(request: Request, logged_in: auth.LoggedIn, tema_id: int):
|
||||||
return temes.tema(request, tema_id, logged_in)
|
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) |
|
||||||
|
temes_q.tema_compute_stats |
|
||||||
|
links_service.add_links_to_tema |
|
||||||
|
lyrics_service.add_lyrics_to_tema |
|
||||||
|
properties_service.add_properties_to_tema
|
||||||
|
).result()
|
||||||
|
return temes.tema(request, logged_in, tema)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/api/tema/{tema_id}")
|
@router.delete("/api/tema/{tema_id}")
|
||||||
@@ -62,190 +75,6 @@ def set_title(
|
|||||||
return tema.title(request=request, tema=new_tema, logged_in=logged_in)
|
return tema.title(request=request, tema=new_tema, logged_in=logged_in)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/tema/{tema_id}/lyric/{lyric_id}")
|
|
||||||
def lyric(request: Request, logged_in: auth.LoggedIn, tema_id: int, lyric_id: int):
|
|
||||||
return tema.lyric(request=request, logged_in=logged_in, tema_id=tema_id, lyric_id=lyric_id)
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/api/tema/{tema_id}/lyric/{lyric_id}")
|
|
||||||
def set_lyric(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.RequireLogin,
|
|
||||||
tema_id: int,
|
|
||||||
lyric_id: int,
|
|
||||||
title: Annotated[str, Form()],
|
|
||||||
lyric: Annotated[str, Form()],
|
|
||||||
):
|
|
||||||
new_lyric = model.Lyrics(id=lyric_id, title=title, content=lyric.strip())
|
|
||||||
_ = temes_w.update_lyric(tema_id=tema_id, lyric_id=lyric_id, lyric=new_lyric)
|
|
||||||
return tema.lyric(request=request, logged_in=logged_in, tema_id=tema_id, lyric_id=lyric_id)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/tema/{tema_id}/lyric")
|
|
||||||
def add_lyric(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.RequireLogin,
|
|
||||||
tema_id: int,
|
|
||||||
):
|
|
||||||
new_tema = temes_w.add_lyric(tema_id=tema_id)
|
|
||||||
lyric_id = new_tema.lyrics[-1].id
|
|
||||||
if lyric_id is None:
|
|
||||||
raise RuntimeError("Invalid lyric_id on newly created lyric!")
|
|
||||||
return tema.lyric_editor(
|
|
||||||
request=request,
|
|
||||||
logged_in=logged_in,
|
|
||||||
tema_id=tema_id,
|
|
||||||
lyric_id=lyric_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/api/tema/{tema_id}/lyric/{lyric_id}")
|
|
||||||
def delete_lyric(
|
|
||||||
tema_id: int,
|
|
||||||
lyric_id: int,
|
|
||||||
):
|
|
||||||
_ = temes_w.delete_lyric(tema_id=tema_id, lyric_id=lyric_id)
|
|
||||||
return HTMLResponse()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/tema/{tema_id}/link/{link_id}")
|
|
||||||
def link(request: Request, logged_in: auth.LoggedIn, tema_id: int, link_id: int):
|
|
||||||
return tema.link(request=request, logged_in=logged_in, tema_id=tema_id, link_id=link_id)
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/api/tema/{tema_id}/link/{link_id}")
|
|
||||||
async def set_link(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.RequireLogin,
|
|
||||||
tema_id: int,
|
|
||||||
link_id: int,
|
|
||||||
content_type: Annotated[model.ContentType, Form()],
|
|
||||||
title: Annotated[str | None, Form()] = None,
|
|
||||||
url: Annotated[str | None, Form()] = None,
|
|
||||||
upload_file: Annotated[UploadFile | None, File()] = None,
|
|
||||||
):
|
|
||||||
if upload_file:
|
|
||||||
url = await files.store_file(tema_id=tema_id, upload_file=upload_file)
|
|
||||||
|
|
||||||
link_type = guess_link_type(url or '')
|
|
||||||
new_link = model.Link(
|
|
||||||
id=link_id,
|
|
||||||
content_type=content_type,
|
|
||||||
link_type=link_type,
|
|
||||||
url=url or '',
|
|
||||||
title=title or '',
|
|
||||||
)
|
|
||||||
_ = temes_w.update_link(tema_id=tema_id, link_id=link_id, link=new_link)
|
|
||||||
return tema.link(request=request, logged_in=logged_in, tema_id=tema_id, link_id=link_id)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/tema/{tema_id}/link")
|
|
||||||
def add_link(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.RequireLogin,
|
|
||||||
tema_id: int,
|
|
||||||
):
|
|
||||||
new_tema = temes_w.add_link(tema_id=tema_id)
|
|
||||||
link_id = new_tema.links[-1].id
|
|
||||||
if link_id is None:
|
|
||||||
raise RuntimeError("Invalid link_id on newly created link!")
|
|
||||||
return tema.link_editor(
|
|
||||||
request=request,
|
|
||||||
logged_in=logged_in,
|
|
||||||
tema_id=tema_id,
|
|
||||||
link_id=link_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/api/tema/{tema_id}/link/{link_id}")
|
|
||||||
def delete_link(
|
|
||||||
_: auth.RequireLogin,
|
|
||||||
tema_id: int,
|
|
||||||
link_id: int,
|
|
||||||
):
|
|
||||||
_tema = temes_w.delete_link(tema_id=tema_id, link_id=link_id)
|
|
||||||
return HTMLResponse(
|
|
||||||
headers={
|
|
||||||
"HX-Trigger": f"reload-tema-{tema_id}-score"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/tema/{tema_id}/link/{link_id}/icon")
|
|
||||||
def link_icon(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.LoggedIn,
|
|
||||||
tema_id: int,
|
|
||||||
link_id: int,
|
|
||||||
content_type: Annotated[model.ContentType, Param()],
|
|
||||||
url: Annotated[str, Param()],
|
|
||||||
):
|
|
||||||
return tema.link_icon(
|
|
||||||
request=request,
|
|
||||||
logged_in=logged_in,
|
|
||||||
tema_id=tema_id,
|
|
||||||
link_id=link_id,
|
|
||||||
url=url,
|
|
||||||
content_type=content_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/tema/{tema_id}/score")
|
|
||||||
def get_score(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.LoggedIn,
|
|
||||||
tema_id: int,
|
|
||||||
):
|
|
||||||
return tema.score(
|
|
||||||
request=request,
|
|
||||||
logged_in=logged_in,
|
|
||||||
tema_id=tema_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/tema/{tema_id}/property/{property_id}")
|
|
||||||
def property_(request: Request, logged_in: auth.LoggedIn, tema_id: int, property_id: int):
|
|
||||||
return tema.property_(request=request, logged_in=logged_in, tema_id=tema_id, property_id=property_id)
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/api/tema/{tema_id}/property/{property_id}")
|
|
||||||
def set_property(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.RequireLogin,
|
|
||||||
tema_id: int,
|
|
||||||
property_id: int,
|
|
||||||
field: Annotated[model.PropertyField, Form()],
|
|
||||||
value: Annotated[str, Form()],
|
|
||||||
):
|
|
||||||
new_property = model.Property(id=property_id, field=field, value=value.strip())
|
|
||||||
_ = temes_w.update_property(tema_id=tema_id, property_id=property_id, prop=new_property)
|
|
||||||
return tema.property_(request=request, logged_in=logged_in, tema_id=tema_id, property_id=property_id)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/tema/{tema_id}/property")
|
|
||||||
def add_property(
|
|
||||||
request: Request,
|
|
||||||
logged_in: auth.RequireLogin,
|
|
||||||
tema_id: int,
|
|
||||||
):
|
|
||||||
new_tema = temes_w.add_property(tema_id=tema_id)
|
|
||||||
property_id = new_tema.properties[-1].id
|
|
||||||
if property_id is None:
|
|
||||||
raise RuntimeError("Invalid property_id on newly created property!")
|
|
||||||
return tema.property_editor(
|
|
||||||
request=request,
|
|
||||||
logged_in=logged_in,
|
|
||||||
tema_id=tema_id,
|
|
||||||
property_id=property_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/api/tema/{tema_id}/property/{property_id}")
|
|
||||||
def delete_property(_: auth.RequireLogin, tema_id: int, property_id: int):
|
|
||||||
_tema = temes_w.delete_property(tema_id=tema_id, property_id=property_id)
|
|
||||||
return HTMLResponse()
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/api/tema/{tema_id}/visible")
|
@router.put("/api/tema/{tema_id}/visible")
|
||||||
def set_visible(request: Request, logged_in: auth.RequireLogin, tema_id: int):
|
def set_visible(request: Request, logged_in: auth.RequireLogin, tema_id: int):
|
||||||
new_tema = temes_w.set_visibility(tema_id=tema_id, hidden=False)
|
new_tema = temes_w.set_visibility(tema_id=tema_id, hidden=False)
|
||||||
|
|||||||
160
folkugat_web/api/tema/links.py
Normal file
160
folkugat_web/api/tema/links.py
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import HTTPException, Request, UploadFile
|
||||||
|
from fastapi.params import File, Form, Param
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from folkugat_web.api import router
|
||||||
|
from folkugat_web.fragments.tema import links as links_fragments
|
||||||
|
from folkugat_web.model import temes as model
|
||||||
|
from folkugat_web.services import auth, files
|
||||||
|
from folkugat_web.services.temes import links as links_service
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/tema/{tema_id}/link/{link_id}")
|
||||||
|
def link(request: Request, logged_in: auth.LoggedIn, tema_id: int, link_id: int):
|
||||||
|
link = links_service.get_link_by_id(link_id=link_id, tema_id=tema_id)
|
||||||
|
if not link:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find link!")
|
||||||
|
return links_fragments.link(request=request, logged_in=logged_in, link=link)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/api/tema/{tema_id}/link/{link_id}")
|
||||||
|
async def set_link(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
tema_id: int,
|
||||||
|
link_id: int,
|
||||||
|
content_type: Annotated[model.ContentType, Form()],
|
||||||
|
title: Annotated[str | None, Form()] = None,
|
||||||
|
url: Annotated[str | None, Form()] = None,
|
||||||
|
upload_file: Annotated[UploadFile | None, File()] = None,
|
||||||
|
):
|
||||||
|
if upload_file:
|
||||||
|
url = await files.store_file(tema_id=tema_id, upload_file=upload_file)
|
||||||
|
|
||||||
|
link_type = links_service.guess_link_type(url or '')
|
||||||
|
new_link = model.Link(
|
||||||
|
id=link_id,
|
||||||
|
tema_id=tema_id,
|
||||||
|
content_type=content_type,
|
||||||
|
link_type=link_type,
|
||||||
|
url=(url or '').strip(),
|
||||||
|
title=(title or '').strip(),
|
||||||
|
)
|
||||||
|
links_service.update_link(link=new_link)
|
||||||
|
return links_fragments.link(request=request, logged_in=logged_in, link=new_link)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/tema/{tema_id}/link")
|
||||||
|
def create_link(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
tema_id: int,
|
||||||
|
):
|
||||||
|
link = links_service.create_link(tema_id=tema_id)
|
||||||
|
return links_fragments.link_editor(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
link=link,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/api/tema/{tema_id}/link/{link_id}")
|
||||||
|
def delete_link(
|
||||||
|
_logged_in: auth.RequireLogin,
|
||||||
|
tema_id: int,
|
||||||
|
link_id: int,
|
||||||
|
):
|
||||||
|
links_service.delete_link(link_id=link_id, tema_id=tema_id)
|
||||||
|
return HTMLResponse(
|
||||||
|
headers={
|
||||||
|
"HX-Trigger": f"reload-tema-{tema_id}-score"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/tema/{tema_id}/link/{link_id}/icon")
|
||||||
|
def link_icon(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.LoggedIn,
|
||||||
|
tema_id: int,
|
||||||
|
link_id: int,
|
||||||
|
content_type: Annotated[model.ContentType, Param()],
|
||||||
|
url: Annotated[str, Param()],
|
||||||
|
):
|
||||||
|
link = model.Link(
|
||||||
|
id=link_id,
|
||||||
|
tema_id=tema_id,
|
||||||
|
content_type=content_type,
|
||||||
|
link_type=links_service.guess_link_type(url),
|
||||||
|
url=url,
|
||||||
|
)
|
||||||
|
return links_fragments.link_icon(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
link=link,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/tema/{tema_id}/score")
|
||||||
|
def get_score(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.LoggedIn,
|
||||||
|
tema_id: int,
|
||||||
|
):
|
||||||
|
return links_fragments.score(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
tema_id=tema_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/tema/{tema_id}/editor/link/{link_id}")
|
||||||
|
def link_editor(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
tema_id: int,
|
||||||
|
link_id: int,
|
||||||
|
):
|
||||||
|
link = links_service.get_link_by_id(link_id=link_id, tema_id=tema_id)
|
||||||
|
if not link:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find link!")
|
||||||
|
return links_fragments.link_editor(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
link=link,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/tema/{tema_id}/editor/link/{link_id}/url")
|
||||||
|
def link_editor_url_input(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
tema_id: int,
|
||||||
|
link_id: int,
|
||||||
|
):
|
||||||
|
link = links_service.get_link_by_id(link_id=link_id, tema_id=tema_id)
|
||||||
|
if not link:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find link!")
|
||||||
|
return links_fragments.link_editor_url(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
link=link,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/tema/{tema_id}/editor/link/{link_id}/file")
|
||||||
|
def link_editor_file_input(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
tema_id: int,
|
||||||
|
link_id: int,
|
||||||
|
):
|
||||||
|
link = links_service.get_link_by_id(link_id=link_id, tema_id=tema_id)
|
||||||
|
if not link:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find link!")
|
||||||
|
return links_fragments.link_editor_file(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
link=link,
|
||||||
|
)
|
||||||
80
folkugat_web/api/tema/lyrics.py
Normal file
80
folkugat_web/api/tema/lyrics.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import dataclasses
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import HTTPException, Request
|
||||||
|
from fastapi.params import Form
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from folkugat_web.api import router
|
||||||
|
from folkugat_web.fragments.tema import lyrics as lyrics_fragments
|
||||||
|
from folkugat_web.services import auth
|
||||||
|
from folkugat_web.services.temes import lyrics as lyrics_service
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/tema/{tema_id}/lyric/{lyric_id}")
|
||||||
|
def lyric(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.LoggedIn,
|
||||||
|
tema_id: int,
|
||||||
|
lyric_id: int,
|
||||||
|
):
|
||||||
|
lyric = lyrics_service.get_lyric_by_id(lyric_id=lyric_id, tema_id=tema_id)
|
||||||
|
if not lyric:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||||
|
return lyrics_fragments.lyric(request=request, logged_in=logged_in, lyric=lyric)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/api/tema/{tema_id}/lyric/{lyric_id}")
|
||||||
|
def set_lyric(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
tema_id: int,
|
||||||
|
lyric_id: int,
|
||||||
|
title: Annotated[str, Form()],
|
||||||
|
content: Annotated[str, Form()],
|
||||||
|
):
|
||||||
|
lyric = lyrics_service.get_lyric_by_id(lyric_id=lyric_id, tema_id=tema_id)
|
||||||
|
if not lyric:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||||
|
new_lyric = dataclasses.replace(lyric, title=title, content=content.strip())
|
||||||
|
lyrics_service.update_lyric(lyric=new_lyric)
|
||||||
|
return lyrics_fragments.lyric(request=request, logged_in=logged_in, lyric=new_lyric)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/tema/{tema_id}/lyric")
|
||||||
|
def add_lyric(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
tema_id: int,
|
||||||
|
):
|
||||||
|
lyric = lyrics_service.create_lyric(tema_id=tema_id)
|
||||||
|
return lyrics_fragments.lyric_editor(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
lyric=lyric,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/api/tema/{tema_id}/lyric/{lyric_id}")
|
||||||
|
def delete_lyric(
|
||||||
|
tema_id: int,
|
||||||
|
lyric_id: int,
|
||||||
|
):
|
||||||
|
lyrics_service.delete_lyric(lyric_id=lyric_id, tema_id=tema_id)
|
||||||
|
return HTMLResponse()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/tema/{tema_id}/editor/lyric/{lyric_id}")
|
||||||
|
def lyric_editor(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
tema_id: int,
|
||||||
|
lyric_id: int,
|
||||||
|
):
|
||||||
|
lyric = lyrics_service.get_lyric_by_id(lyric_id=lyric_id, tema_id=tema_id)
|
||||||
|
if not lyric:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||||
|
return lyrics_fragments.lyric_editor(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
lyric=lyric,
|
||||||
|
)
|
||||||
73
folkugat_web/api/tema/properties.py
Normal file
73
folkugat_web/api/tema/properties.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import dataclasses
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import HTTPException, Request
|
||||||
|
from fastapi.params import Form
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from folkugat_web.api import router
|
||||||
|
from folkugat_web.fragments.tema import properties as properties_fragments
|
||||||
|
from folkugat_web.model import temes as model
|
||||||
|
from folkugat_web.services import auth
|
||||||
|
from folkugat_web.services.temes import properties as properties_service
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/tema/{tema_id}/property/{property_id}")
|
||||||
|
def property_(request: Request, logged_in: auth.LoggedIn, tema_id: int, property_id: int):
|
||||||
|
property = properties_service.get_property_by_id(property_id=property_id, tema_id=tema_id)
|
||||||
|
if not property:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||||
|
return properties_fragments.property_(request=request, logged_in=logged_in, property=property)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/api/tema/{tema_id}/property/{property_id}")
|
||||||
|
def set_property(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
tema_id: int,
|
||||||
|
property_id: int,
|
||||||
|
field: Annotated[model.PropertyField, Form()],
|
||||||
|
value: Annotated[str, Form()],
|
||||||
|
):
|
||||||
|
property = properties_service.get_property_by_id(property_id=property_id, tema_id=tema_id)
|
||||||
|
if not property:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||||
|
new_property = dataclasses.replace(property, field=field, value=value.strip())
|
||||||
|
properties_service.update_property(property=new_property)
|
||||||
|
return properties_fragments.property_(request=request, logged_in=logged_in, property=new_property)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/tema/{tema_id}/property")
|
||||||
|
def add_property(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
tema_id: int,
|
||||||
|
):
|
||||||
|
property = properties_service.create_property(tema_id=tema_id)
|
||||||
|
return properties_fragments.property_editor(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
property=property,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/api/tema/{tema_id}/property/{property_id}")
|
||||||
|
def delete_property(_: auth.RequireLogin, tema_id: int, property_id: int):
|
||||||
|
properties_service.delete_property(property_id=property_id, tema_id=tema_id)
|
||||||
|
return HTMLResponse()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api/tema/{tema_id}/editor/property/{property_id}")
|
||||||
|
def property_editor(
|
||||||
|
request: Request,
|
||||||
|
logged_in: auth.RequireLogin,
|
||||||
|
tema_id: int,
|
||||||
|
property_id: int,
|
||||||
|
):
|
||||||
|
property = properties_service.get_property_by_id(property_id=property_id, tema_id=tema_id)
|
||||||
|
if not property:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find lyric!")
|
||||||
|
return properties_fragments.property_editor(
|
||||||
|
request=request,
|
||||||
|
logged_in=logged_in,
|
||||||
|
property=property,
|
||||||
|
)
|
||||||
@@ -37,4 +37,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<span><span id="page_num">?</span> / <span id="page_count">?</span></span>
|
<span><span id="page_num">?</span> / <span id="page_count">?</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-beige border rounded border-beige m-2 p-1">
|
||||||
|
<a href="{{ pdf_url }}" target="_blank">Obre el PDF</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,14 +36,14 @@
|
|||||||
<div class="m-2 text-sm text-beige">
|
<div class="m-2 text-sm text-beige">
|
||||||
<button title="Desa els canvis"
|
<button title="Desa els canvis"
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
hx-put="/api/tema/{{ tema.id }}/link/{{ link.id }}"
|
hx-put="/api/tema/{{ link.tema_id }}/link/{{ link.id }}"
|
||||||
hx-target="#tema-link-{{ link.id }}"
|
hx-target="#tema-link-{{ link.id }}"
|
||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML">
|
||||||
<i class="fa fa-check" aria-hidden="true"></i>
|
<i class="fa fa-check" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<button title="Descarta els canvis"
|
<button title="Descarta els canvis"
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
hx-get="/api/tema/{{ tema.id }}/link/{{ link.id }}"
|
hx-get="/api/tema/{{ link.tema_id }}/link/{{ link.id }}"
|
||||||
hx-target="#tema-link-{{ link.id }}"
|
hx-target="#tema-link-{{ link.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>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div id="link-editor-{{ link_id }}-file"
|
<div id="link-editor-{{ link.id }}-file"
|
||||||
class="flex flex-row gap-2">
|
class="flex flex-row gap-2">
|
||||||
<input type='file'
|
<input type='file'
|
||||||
class="border border-beige focus:outline-none
|
class="border border-beige focus:outline-none
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
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/{{ tema.id }}/editor/link/{{ link_id }}/url"
|
hx-get="/api/tema/{{ link.tema_id }}/editor/link/{{ link.id }}/url"
|
||||||
hx-target="#link-editor-{{ link_id }}-file"
|
hx-target="#link-editor-{{ link.id }}-file"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
>
|
>
|
||||||
<i class="fa fa-link" aria-hidden="true"></i>
|
<i class="fa fa-link" aria-hidden="true"></i>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div id="link-editor-{{ link_id }}-url"
|
<div id="link-editor-{{ link.id }}-url"
|
||||||
class="flex flex-row gap-2">
|
class="flex flex-row gap-2">
|
||||||
<input name="url"
|
<input name="url"
|
||||||
placeholder="URL de l'enllaç"
|
placeholder="URL de l'enllaç"
|
||||||
@@ -6,7 +6,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"
|
||||||
hx-get="/api/tema/{{ tema.id }}/link/{{ link_id }}/icon"
|
hx-get="/api/tema/{{ link.tema_id }}/link/{{ link.id }}/icon"
|
||||||
hx-trigger="keyup delay:500ms changed"
|
hx-trigger="keyup delay:500ms changed"
|
||||||
hx-target="#link-icon-{{ link.id }}"
|
hx-target="#link-icon-{{ link.id }}"
|
||||||
hx-include="[name='content_type']"
|
hx-include="[name='content_type']"
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
/>
|
/>
|
||||||
<button title="Puja un fitxer"
|
<button title="Puja un fitxer"
|
||||||
class="border border-beige rounded py-1 px-2 my-1"
|
class="border border-beige rounded py-1 px-2 my-1"
|
||||||
hx-get="/api/tema/{{ tema.id }}/editor/link/{{ link_id }}/file"
|
hx-get="/api/tema/{{ link.tema_id }}/editor/link/{{ link.id }}/file"
|
||||||
hx-target="#link-editor-{{ link_id }}-url"
|
hx-target="#link-editor-{{ link.id }}-url"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
>
|
>
|
||||||
<i class="fa fa-upload" aria-hidden="true"></i>
|
<i class="fa fa-upload" aria-hidden="true"></i>
|
||||||
|
|||||||
@@ -9,21 +9,21 @@
|
|||||||
/>
|
/>
|
||||||
<button title="Desa els canvis"
|
<button title="Desa els canvis"
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
hx-put="/api/tema/{{ tema.id }}/lyric/{{ lyric.id }}"
|
hx-put="/api/tema/{{ lyric.tema_id }}/lyric/{{ lyric.id }}"
|
||||||
hx-target="#tema-lyric-{{ lyric.id }}"
|
hx-target="#tema-lyric-{{ lyric.id }}"
|
||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML">
|
||||||
<i class="fa fa-check" aria-hidden="true"></i>
|
<i class="fa fa-check" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<button title="Descarta els canvis"
|
<button title="Descarta els canvis"
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
hx-get="/api/tema/{{ tema.id }}/lyric/{{ lyric.id }}"
|
hx-get="/api/tema/{{ lyric.tema_id }}/lyric/{{ lyric.id }}"
|
||||||
hx-target="#tema-lyric-{{ lyric.id }}"
|
hx-target="#tema-lyric-{{ lyric.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>
|
||||||
</button>
|
</button>
|
||||||
</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">
|
||||||
<textarea name="lyric"
|
<textarea name="content"
|
||||||
placeholder="Lletra"
|
placeholder="Lletra"
|
||||||
rows="{{ lyric.content.count('\n') + 1 }}"
|
rows="{{ lyric.content.count('\n') + 1 }}"
|
||||||
class="border border-beige focus:outline-none
|
class="border border-beige focus:outline-none
|
||||||
|
|||||||
@@ -30,14 +30,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<button title="Desa els canvis"
|
<button title="Desa els canvis"
|
||||||
class="text-sm text-beige mx-1"
|
class="text-sm text-beige mx-1"
|
||||||
hx-put="/api/tema/{{ tema.id }}/property/{{ property.id }}"
|
hx-put="/api/tema/{{ property.tema_id }}/property/{{ property.id }}"
|
||||||
hx-target="#tema-property-{{ property.id }}"
|
hx-target="#tema-property-{{ property.id }}"
|
||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML">
|
||||||
<i class="fa fa-check" aria-hidden="true"></i>
|
<i class="fa fa-check" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<button title="Descarta els canvis"
|
<button title="Descarta els canvis"
|
||||||
class="text-sm text-beige mx-1"
|
class="text-sm text-beige mx-1"
|
||||||
hx-get="/api/tema/{{ tema.id }}/property/{{ property.id }}"
|
hx-get="/api/tema/{{ property.tema_id }}/property/{{ property.id }}"
|
||||||
hx-target="#tema-property-{{ property.id }}"
|
hx-target="#tema-property-{{ property.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>
|
||||||
|
|||||||
@@ -14,14 +14,14 @@
|
|||||||
<div class="m-2 text-sm text-beige">
|
<div class="m-2 text-sm text-beige">
|
||||||
<button title="Modifica l'enllaç"
|
<button title="Modifica l'enllaç"
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
hx-get="/api/tema/{{ tema.id }}/editor/link/{{ link.id }}"
|
hx-get="/api/tema/{{ link.tema_id }}/editor/link/{{ link.id }}"
|
||||||
hx-target="#tema-link-{{ link.id }}"
|
hx-target="#tema-link-{{ link.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 l'enllaç"
|
<button title="Esborra l'enllaç"
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
hx-delete="/api/tema/{{ tema.id }}/link/{{ link.id }}"
|
hx-delete="/api/tema/{{ link.tema_id }}/link/{{ link.id }}"
|
||||||
hx-target="#tema-link-{{ link.id }}"
|
hx-target="#tema-link-{{ link.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>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
{% if logged_in %}
|
{% if logged_in %}
|
||||||
<div class="flex flex-row my-2 justify-end">
|
<div class="flex flex-row my-2 justify-end">
|
||||||
<button title="Afegeix una lletra"
|
<button title="Afegeix un enllaç"
|
||||||
class="text-sm text-beige text-right"
|
class="text-sm text-beige text-right"
|
||||||
hx-post="/api/tema/{{ tema.id }}/link"
|
hx-post="/api/tema/{{ tema.id }}/link"
|
||||||
hx-target="#new-link-target"
|
hx-target="#new-link-target"
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
{% if logged_in %}
|
{% if logged_in %}
|
||||||
<button title="Modifica la lletra"
|
<button title="Modifica la lletra"
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
hx-get="/api/tema/{{ tema.id }}/editor/lyric/{{ lyric.id }}"
|
hx-get="/api/tema/{{ lyric.tema_id }}/editor/lyric/{{ lyric.id }}"
|
||||||
hx-target="#tema-lyric-{{ lyric.id }}"
|
hx-target="#tema-lyric-{{ lyric.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 la lletra"
|
<button title="Esborra la lletra"
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
hx-delete="/api/tema/{{ tema.id }}/lyric/{{ lyric.id }}"
|
hx-delete="/api/tema/{{ lyric.tema_id }}/lyric/{{ lyric.id }}"
|
||||||
hx-target="#tema-lyric-{{ lyric.id }}"
|
hx-target="#tema-lyric-{{ lyric.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>
|
||||||
|
|||||||
@@ -11,14 +11,14 @@
|
|||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
<button title="Modifica la informació"
|
<button title="Modifica la informació"
|
||||||
class="text-sm text-beige mx-1"
|
class="text-sm text-beige mx-1"
|
||||||
hx-get="/api/tema/{{ tema.id }}/editor/property/{{ property.id }}"
|
hx-get="/api/tema/{{ property.tema_id }}/editor/property/{{ property.id }}"
|
||||||
hx-target="#tema-property-{{ property.id }}"
|
hx-target="#tema-property-{{ property.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 la informació"
|
<button title="Esborra la informació"
|
||||||
class="text-sm text-beige mx-1"
|
class="text-sm text-beige mx-1"
|
||||||
hx-delete="/api/tema/{{ tema.id }}/property/{{ property.id }}"
|
hx-delete="/api/tema/{{ property.tema_id }}/property/{{ property.id }}"
|
||||||
hx-target="#tema-property-{{ property.id }}"
|
hx-target="#tema-property-{{ property.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>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
|
from collections.abc import Iterator
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import Iterator, Optional
|
|
||||||
|
|
||||||
from folkugat_web.config.db import DB_FILE
|
from folkugat_web.config.db import DB_FILE
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ def get_connection(con: Connection | None = None) -> Iterator[Connection]:
|
|||||||
yield con
|
yield con
|
||||||
else:
|
else:
|
||||||
con = sqlite3.connect(DB_FILE)
|
con = sqlite3.connect(DB_FILE)
|
||||||
|
_ = con.execute('PRAGMA foreign_keys = ON;')
|
||||||
try:
|
try:
|
||||||
yield con
|
yield con
|
||||||
con.commit()
|
con.commit()
|
||||||
|
|||||||
@@ -6,12 +6,6 @@ def create_db(con: Connection | None = None):
|
|||||||
create_playlists_table(con)
|
create_playlists_table(con)
|
||||||
|
|
||||||
|
|
||||||
def drop_playlists_table(con: Connection):
|
|
||||||
query = "DROP TABLE IF EXISTS playlists"
|
|
||||||
cur = con.cursor()
|
|
||||||
_ = cur.execute(query)
|
|
||||||
|
|
||||||
|
|
||||||
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 (
|
||||||
|
|||||||
@@ -6,12 +6,6 @@ def create_db(con: Connection | None = None):
|
|||||||
create_sessions_table(con)
|
create_sessions_table(con)
|
||||||
|
|
||||||
|
|
||||||
def drop_sessions_table(con: Connection):
|
|
||||||
query = "DROP TABLE IF EXISTS sessions"
|
|
||||||
cur = con.cursor()
|
|
||||||
_ = cur.execute(query)
|
|
||||||
|
|
||||||
|
|
||||||
def create_sessions_table(con: Connection):
|
def create_sessions_table(con: Connection):
|
||||||
query = """
|
query = """
|
||||||
CREATE TABLE IF NOT EXISTS sessions (
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
|
|||||||
@@ -2,21 +2,16 @@ import datetime
|
|||||||
import json
|
import json
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
from folkugat_web.model import IndexedList
|
|
||||||
from folkugat_web.model import search as search_model
|
from folkugat_web.model import search as search_model
|
||||||
from folkugat_web.model import temes as model
|
from folkugat_web.model import temes as model
|
||||||
|
|
||||||
TemaRowTuple = tuple[int, str, str, str, str, str, str, str, str, int]
|
TemaRowTuple = tuple[int, str, str, str, str, int]
|
||||||
|
|
||||||
|
|
||||||
class TemaRowDict(TypedDict):
|
class TemaRowDict(TypedDict):
|
||||||
id: int | None
|
id: int | None
|
||||||
title: str
|
title: str
|
||||||
properties: str
|
|
||||||
links: str
|
|
||||||
lyrics: str
|
|
||||||
alternatives: str
|
alternatives: str
|
||||||
ngrams: str
|
|
||||||
modification_date: str
|
modification_date: str
|
||||||
creation_date: str
|
creation_date: str
|
||||||
hidden: int
|
hidden: int
|
||||||
@@ -26,31 +21,24 @@ def tema_to_row(tema: model.Tema) -> TemaRowDict:
|
|||||||
return {
|
return {
|
||||||
'id': tema.id,
|
'id': tema.id,
|
||||||
'title': tema.title,
|
'title': tema.title,
|
||||||
'properties': json.dumps(list(map(lambda p: p.to_dict(), tema.properties))),
|
|
||||||
'links': json.dumps(list(map(lambda l: l.to_dict(), tema.links))),
|
|
||||||
'lyrics': json.dumps(list(map(lambda l: l.to_dict(), tema.lyrics))),
|
|
||||||
'alternatives': json.dumps(tema.alternatives),
|
'alternatives': json.dumps(tema.alternatives),
|
||||||
'ngrams': json.dumps(tema.ngrams),
|
|
||||||
'modification_date': tema.modification_date.isoformat(),
|
'modification_date': tema.modification_date.isoformat(),
|
||||||
'creation_date': tema.creation_date.isoformat(),
|
'creation_date': tema.creation_date.isoformat(),
|
||||||
'hidden': 1 if tema.hidden else 0,
|
'hidden': 1 if tema.hidden else 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def cell_to_ngrams(cell: str) -> search_model.NGrams:
|
def cell_to_ngrams(cell_str: str) -> search_model.NGrams:
|
||||||
return {int(n): ngrams_ for n, ngrams_ in json.loads(cell).items()}
|
cell: dict[str, list[str]] = json.loads(cell_str)
|
||||||
|
return {int(n): ngrams_ for n, ngrams_ in cell.items()}
|
||||||
|
|
||||||
|
|
||||||
def row_to_tema(row: TemaRowTuple) -> model.Tema:
|
def row_to_tema(row: TemaRowTuple) -> model.Tema:
|
||||||
return model.Tema(
|
return model.Tema(
|
||||||
id=row[0],
|
id=row[0],
|
||||||
title=row[1],
|
title=row[1],
|
||||||
properties=IndexedList(map(model.Property.from_dict, json.loads(row[2]))),
|
alternatives=json.loads(row[2]),
|
||||||
links=IndexedList(map(model.Link.from_dict, json.loads(row[3]))),
|
modification_date=datetime.datetime.fromisoformat(row[3]),
|
||||||
lyrics=IndexedList(map(model.Lyrics.from_dict, json.loads(row[4]))),
|
creation_date=datetime.datetime.fromisoformat(row[4]),
|
||||||
alternatives=json.loads(row[5]),
|
hidden=bool(row[5]),
|
||||||
ngrams=cell_to_ngrams(row[6]),
|
|
||||||
modification_date=datetime.datetime.fromisoformat(row[7]),
|
|
||||||
creation_date=datetime.datetime.fromisoformat(row[8]),
|
|
||||||
hidden=bool(row[9]),
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,12 +4,9 @@ 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_temes_table(con)
|
create_temes_table(con)
|
||||||
|
create_links_table(con)
|
||||||
|
create_lyrics_table(con)
|
||||||
def drop_temes_table(con: Connection):
|
create_properties_table(con)
|
||||||
query = "DROP TABLE IF EXISTS temes"
|
|
||||||
cur = con.cursor()
|
|
||||||
_ = cur.execute(query)
|
|
||||||
|
|
||||||
|
|
||||||
def create_temes_table(con: Connection):
|
def create_temes_table(con: Connection):
|
||||||
@@ -17,11 +14,7 @@ def create_temes_table(con: Connection):
|
|||||||
CREATE TABLE IF NOT EXISTS temes (
|
CREATE TABLE IF NOT EXISTS temes (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
properties TEXT,
|
|
||||||
links TEXT,
|
|
||||||
lyrics TEXT,
|
|
||||||
alternatives TEXT,
|
alternatives TEXT,
|
||||||
ngrams TEXT,
|
|
||||||
creation_date TEXT NOT NULL,
|
creation_date TEXT NOT NULL,
|
||||||
modification_date TEXT NOT NULL,
|
modification_date TEXT NOT NULL,
|
||||||
hidden INTEGER NOT NULL
|
hidden INTEGER NOT NULL
|
||||||
@@ -29,3 +22,47 @@ def create_temes_table(con: Connection):
|
|||||||
"""
|
"""
|
||||||
cur = con.cursor()
|
cur = con.cursor()
|
||||||
_ = cur.execute(query)
|
_ = cur.execute(query)
|
||||||
|
|
||||||
|
|
||||||
|
def create_links_table(con: Connection):
|
||||||
|
query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS tema_links (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
tema_id INTEGER,
|
||||||
|
content_type TEXT NOT NULL,
|
||||||
|
link_type TEXT,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(tema_id) REFERENCES temes(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query)
|
||||||
|
|
||||||
|
|
||||||
|
def create_lyrics_table(con: Connection):
|
||||||
|
query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS tema_lyrics (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
tema_id INTEGER,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(tema_id) REFERENCES temes(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query)
|
||||||
|
|
||||||
|
|
||||||
|
def create_properties_table(con: Connection):
|
||||||
|
query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS tema_properties (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
tema_id INTEGER,
|
||||||
|
field TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(tema_id) REFERENCES temes(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query)
|
||||||
|
|||||||
137
folkugat_web/dal/sql/temes/links.py
Normal file
137
folkugat_web/dal/sql/temes/links.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
from collections.abc import Iterable
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from folkugat_web.dal.sql import Connection, get_connection
|
||||||
|
from folkugat_web.model import temes as model
|
||||||
|
from folkugat_web.utils import map_none
|
||||||
|
|
||||||
|
LinkRowTuple = tuple[int, int, str, str | None, str, str]
|
||||||
|
|
||||||
|
|
||||||
|
class LinkRowDict(TypedDict):
|
||||||
|
id: int | None
|
||||||
|
tema_id: int
|
||||||
|
content_type: str
|
||||||
|
link_type: str | None
|
||||||
|
title: str
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
def link_to_row(link: model.Link) -> LinkRowDict:
|
||||||
|
return {
|
||||||
|
"id": link.id,
|
||||||
|
"tema_id": link.tema_id,
|
||||||
|
"content_type": link.content_type.value,
|
||||||
|
"link_type": map_none(lambda lt: lt.value, link.link_type),
|
||||||
|
"title": link.title,
|
||||||
|
"url": link.url,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def row_to_link(row: LinkRowTuple) -> model.Link:
|
||||||
|
return model.Link(
|
||||||
|
id=row[0],
|
||||||
|
tema_id=row[1],
|
||||||
|
content_type=model.ContentType(row[2]),
|
||||||
|
link_type=map_none(model.LinkType, row[3]),
|
||||||
|
title=row[4],
|
||||||
|
url=row[5],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class QueryData(TypedDict, total=False):
|
||||||
|
id: int
|
||||||
|
tema_id: int
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_clause(
|
||||||
|
link_id: int | None,
|
||||||
|
tema_id: int | None,
|
||||||
|
) -> tuple[str, QueryData]:
|
||||||
|
filter_clauses: list[str] = []
|
||||||
|
filter_data: QueryData = {}
|
||||||
|
|
||||||
|
if link_id is not None:
|
||||||
|
filter_clauses.append("id = :id")
|
||||||
|
filter_data["id"] = link_id
|
||||||
|
|
||||||
|
if tema_id is not None:
|
||||||
|
filter_clauses.append("tema_id = :tema_id")
|
||||||
|
filter_data["tema_id"] = tema_id
|
||||||
|
|
||||||
|
filter_clause = " AND ".join(filter_clauses)
|
||||||
|
return filter_clause, filter_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_links(link_id: int | None = None, tema_id: int | None = None, con: Connection | None = None) -> Iterable[model.Link]:
|
||||||
|
filter_clause, data = _filter_clause(link_id=link_id, tema_id=tema_id)
|
||||||
|
if filter_clause:
|
||||||
|
filter_clause = f"WHERE {filter_clause}"
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
id, tema_id, content_type, link_type, title, url
|
||||||
|
FROM tema_links
|
||||||
|
{filter_clause}
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
return map(row_to_link, cur.fetchall())
|
||||||
|
|
||||||
|
|
||||||
|
def insert_link(link: model.Link, con: Connection | None = None) -> model.Link:
|
||||||
|
data = link_to_row(link)
|
||||||
|
query = f"""
|
||||||
|
INSERT INTO tema_links
|
||||||
|
(id, tema_id, content_type, link_type, title, url)
|
||||||
|
VALUES
|
||||||
|
(:id, :tema_id, :content_type, :link_type, :title, :url)
|
||||||
|
RETURNING *
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
row: LinkRowTuple = cur.fetchone()
|
||||||
|
return row_to_link(row)
|
||||||
|
|
||||||
|
|
||||||
|
def create_link(tema_id: int, con: Connection | None = None) -> model.Link:
|
||||||
|
new_link = model.Link(
|
||||||
|
id=None,
|
||||||
|
tema_id=tema_id,
|
||||||
|
content_type=model.ContentType.PARTITURA,
|
||||||
|
link_type=None,
|
||||||
|
url="",
|
||||||
|
title="",
|
||||||
|
)
|
||||||
|
return insert_link(new_link, con=con)
|
||||||
|
|
||||||
|
|
||||||
|
def update_link(link: model.Link, con: Connection | None = None):
|
||||||
|
data = link_to_row(link)
|
||||||
|
query = """
|
||||||
|
UPDATE tema_links
|
||||||
|
SET
|
||||||
|
tema_id = :tema_id, content_type = :content_type, link_type = :link_type,
|
||||||
|
title = :title, url = :url
|
||||||
|
WHERE
|
||||||
|
id = :id
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_link(link_id: int, tema_id: int | None = None, con: Connection | None = None):
|
||||||
|
filter_clause, data = _filter_clause(link_id=link_id, tema_id=tema_id)
|
||||||
|
if filter_clause:
|
||||||
|
filter_clause = f"WHERE {filter_clause}"
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
DELETE FROM tema_links
|
||||||
|
{filter_clause}
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
127
folkugat_web/dal/sql/temes/lyrics.py
Normal file
127
folkugat_web/dal/sql/temes/lyrics.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
from collections.abc import Iterable
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from folkugat_web.dal.sql import Connection, get_connection
|
||||||
|
from folkugat_web.model import temes as model
|
||||||
|
|
||||||
|
LyricRowTuple = tuple[int, int, str, str]
|
||||||
|
|
||||||
|
|
||||||
|
class LyricRowDict(TypedDict):
|
||||||
|
id: int | None
|
||||||
|
tema_id: int
|
||||||
|
title: str
|
||||||
|
content: str
|
||||||
|
|
||||||
|
|
||||||
|
def lyric_to_row(lyric: model.Lyrics) -> LyricRowDict:
|
||||||
|
return {
|
||||||
|
"id": lyric.id,
|
||||||
|
"tema_id": lyric.tema_id,
|
||||||
|
"title": lyric.title,
|
||||||
|
"content": lyric.content,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def row_to_lyric(row: LyricRowTuple) -> model.Lyrics:
|
||||||
|
return model.Lyrics(
|
||||||
|
id=row[0],
|
||||||
|
tema_id=row[1],
|
||||||
|
title=row[2],
|
||||||
|
content=row[3],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class QueryData(TypedDict, total=False):
|
||||||
|
id: int
|
||||||
|
tema_id: int
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_clause(
|
||||||
|
lyric_id: int | None,
|
||||||
|
tema_id: int | None,
|
||||||
|
) -> tuple[str, QueryData]:
|
||||||
|
filter_clauses: list[str] = []
|
||||||
|
filter_data: QueryData = {}
|
||||||
|
|
||||||
|
if lyric_id is not None:
|
||||||
|
filter_clauses.append("id = :id")
|
||||||
|
filter_data["id"] = lyric_id
|
||||||
|
|
||||||
|
if tema_id is not None:
|
||||||
|
filter_clauses.append("tema_id = :tema_id")
|
||||||
|
filter_data["tema_id"] = tema_id
|
||||||
|
|
||||||
|
filter_clause = " AND ".join(filter_clauses)
|
||||||
|
return filter_clause, filter_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_lyrics(lyric_id: int | None = None, tema_id: int | None = None, con: Connection | None = None) -> Iterable[model.Lyrics]:
|
||||||
|
filter_clause, data = _filter_clause(lyric_id=lyric_id, tema_id=tema_id)
|
||||||
|
if filter_clause:
|
||||||
|
filter_clause = f"WHERE {filter_clause}"
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
id, tema_id, title, content
|
||||||
|
FROM tema_lyrics
|
||||||
|
{filter_clause}
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
return map(row_to_lyric, cur.fetchall())
|
||||||
|
|
||||||
|
|
||||||
|
def insert_lyric(lyric: model.Lyrics, con: Connection | None = None) -> model.Lyrics:
|
||||||
|
data = lyric_to_row(lyric)
|
||||||
|
query = f"""
|
||||||
|
INSERT INTO tema_lyrics
|
||||||
|
(id, tema_id, title, content)
|
||||||
|
VALUES
|
||||||
|
(:id, :tema_id, :title, :content)
|
||||||
|
RETURNING *
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
row: LyricRowTuple = cur.fetchone()
|
||||||
|
return row_to_lyric(row)
|
||||||
|
|
||||||
|
|
||||||
|
def create_lyric(tema_id: int, title: str | None = None, con: Connection | None = None) -> model.Lyrics:
|
||||||
|
new_lyric = model.Lyrics(
|
||||||
|
id=None,
|
||||||
|
tema_id=tema_id,
|
||||||
|
title=title or "",
|
||||||
|
content="",
|
||||||
|
)
|
||||||
|
return insert_lyric(new_lyric, con=con)
|
||||||
|
|
||||||
|
|
||||||
|
def update_lyric(lyric: model.Lyrics, con: Connection | None = None):
|
||||||
|
data = lyric_to_row(lyric)
|
||||||
|
query = """
|
||||||
|
UPDATE tema_lyrics
|
||||||
|
SET
|
||||||
|
tema_id = :tema_id, title = :title, content = :content
|
||||||
|
WHERE
|
||||||
|
id = :id
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_lyric(lyric_id: int, tema_id: int | None = None, con: Connection | None = None):
|
||||||
|
filter_clause, data = _filter_clause(lyric_id=lyric_id, tema_id=tema_id)
|
||||||
|
if filter_clause:
|
||||||
|
filter_clause = f"WHERE {filter_clause}"
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
DELETE FROM tema_lyrics
|
||||||
|
{filter_clause}
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
127
folkugat_web/dal/sql/temes/properties.py
Normal file
127
folkugat_web/dal/sql/temes/properties.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
from collections.abc import Iterable
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from folkugat_web.dal.sql import Connection, get_connection
|
||||||
|
from folkugat_web.model import temes as model
|
||||||
|
|
||||||
|
PropertyRowTuple = tuple[int, int, str, str]
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyRowDict(TypedDict):
|
||||||
|
id: int | None
|
||||||
|
tema_id: int
|
||||||
|
field: str
|
||||||
|
value: str
|
||||||
|
|
||||||
|
|
||||||
|
def property_to_row(property: model.Property) -> PropertyRowDict:
|
||||||
|
return {
|
||||||
|
"id": property.id,
|
||||||
|
"tema_id": property.tema_id,
|
||||||
|
"field": property.field.value,
|
||||||
|
"value": property.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def row_to_property(row: PropertyRowTuple) -> model.Property:
|
||||||
|
return model.Property(
|
||||||
|
id=row[0],
|
||||||
|
tema_id=row[1],
|
||||||
|
field=model.PropertyField(row[2]),
|
||||||
|
value=row[3],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class QueryData(TypedDict, total=False):
|
||||||
|
id: int
|
||||||
|
tema_id: int
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_clause(
|
||||||
|
property_id: int | None,
|
||||||
|
tema_id: int | None,
|
||||||
|
) -> tuple[str, QueryData]:
|
||||||
|
filter_clauses: list[str] = []
|
||||||
|
filter_data: QueryData = {}
|
||||||
|
|
||||||
|
if property_id is not None:
|
||||||
|
filter_clauses.append("id = :id")
|
||||||
|
filter_data["id"] = property_id
|
||||||
|
|
||||||
|
if tema_id is not None:
|
||||||
|
filter_clauses.append("tema_id = :tema_id")
|
||||||
|
filter_data["tema_id"] = tema_id
|
||||||
|
|
||||||
|
filter_clause = " AND ".join(filter_clauses)
|
||||||
|
return filter_clause, filter_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_properties(property_id: int | None = None, tema_id: int | None = None, con: Connection | None = None) -> Iterable[model.Property]:
|
||||||
|
filter_clause, data = _filter_clause(property_id=property_id, tema_id=tema_id)
|
||||||
|
if filter_clause:
|
||||||
|
filter_clause = f"WHERE {filter_clause}"
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
id, tema_id, field, value
|
||||||
|
FROM tema_properties
|
||||||
|
{filter_clause}
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
return map(row_to_property, cur.fetchall())
|
||||||
|
|
||||||
|
|
||||||
|
def insert_property(property: model.Property, con: Connection | None = None) -> model.Property:
|
||||||
|
data = property_to_row(property)
|
||||||
|
query = f"""
|
||||||
|
INSERT INTO tema_properties
|
||||||
|
(id, tema_id, field, value)
|
||||||
|
VALUES
|
||||||
|
(:id, :tema_id, :field, :value)
|
||||||
|
RETURNING *
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
row: PropertyRowTuple = cur.fetchone()
|
||||||
|
return row_to_property(row)
|
||||||
|
|
||||||
|
|
||||||
|
def create_property(tema_id: int, field: model.PropertyField | None = None, con: Connection | None = None) -> model.Property:
|
||||||
|
new_property = model.Property(
|
||||||
|
id=None,
|
||||||
|
tema_id=tema_id,
|
||||||
|
field=field or model.PropertyField.AUTOR,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
return insert_property(new_property, con=con)
|
||||||
|
|
||||||
|
|
||||||
|
def update_property(property: model.Property, con: Connection | None = None):
|
||||||
|
data = property_to_row(property)
|
||||||
|
query = """
|
||||||
|
UPDATE tema_properties
|
||||||
|
SET
|
||||||
|
tema_id = :tema_id, field = :field, value = :value
|
||||||
|
WHERE
|
||||||
|
id = :id
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_property(property_id: int, tema_id: int | None = None, con: Connection | None = None):
|
||||||
|
filter_clause, data = _filter_clause(property_id=property_id, tema_id=tema_id)
|
||||||
|
if filter_clause:
|
||||||
|
filter_clause = f"WHERE {filter_clause}"
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
DELETE FROM tema_properties
|
||||||
|
{filter_clause}
|
||||||
|
"""
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
@@ -10,8 +10,7 @@ _tema_id_to_ngrams_cache: dict[int, search_model.NGrams] | None = None
|
|||||||
def get_tema_by_id(tema_id: int, con: Connection | None = None) -> model.Tema | None:
|
def get_tema_by_id(tema_id: int, con: Connection | None = None) -> model.Tema | None:
|
||||||
query = """
|
query = """
|
||||||
SELECT
|
SELECT
|
||||||
id, title, properties, links, lyrics, alternatives, ngrams,
|
id, title, alternatives, creation_date, modification_date, hidden
|
||||||
creation_date, modification_date, hidden
|
|
||||||
FROM temes
|
FROM temes
|
||||||
WHERE id = :id
|
WHERE id = :id
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
from folkugat_web.dal.sql import Connection, get_connection
|
from folkugat_web.dal.sql import Connection, get_connection
|
||||||
|
from folkugat_web.model import search as search_model
|
||||||
from folkugat_web.model import temes as model
|
from folkugat_web.model import temes as model
|
||||||
|
|
||||||
from . import conversion
|
from . import conversion
|
||||||
@@ -8,12 +11,10 @@ from .query import evict_tema_id_to_ngrams_cache
|
|||||||
def insert_tema(tema: model.Tema, con: Connection | None = None) -> model.Tema:
|
def insert_tema(tema: model.Tema, con: Connection | None = None) -> model.Tema:
|
||||||
query = """
|
query = """
|
||||||
INSERT INTO temes
|
INSERT INTO temes
|
||||||
(id, title, properties, links, lyrics, alternatives, ngrams,
|
(id, title, alternatives, creation_date, modification_date, hidden)
|
||||||
creation_date, modification_date, hidden)
|
|
||||||
VALUES
|
VALUES
|
||||||
(:id, :title, :properties, :links, :lyrics, :alternatives, :ngrams,
|
(:id, :title, :alternatives, :creation_date, :modification_date, :hidden)
|
||||||
:creation_date, :modification_date, :hidden)
|
RETURNING id, title, alternatives, creation_date, modification_date, hidden
|
||||||
RETURNING *
|
|
||||||
"""
|
"""
|
||||||
data = conversion.tema_to_row(tema)
|
data = conversion.tema_to_row(tema)
|
||||||
with get_connection(con) as con:
|
with get_connection(con) as con:
|
||||||
@@ -28,13 +29,27 @@ def update_tema(tema: model.Tema, con: Connection | None = None):
|
|||||||
query = """
|
query = """
|
||||||
UPDATE temes
|
UPDATE temes
|
||||||
SET
|
SET
|
||||||
title = :title, properties = :properties, links = :links, lyrics = :lyrics,
|
title = :title, alternatives = :alternatives, creation_date = :creation_date,
|
||||||
alternatives = :alternatives, ngrams = :ngrams, creation_date = :creation_date,
|
|
||||||
modification_date = :modification_date, hidden = :hidden
|
modification_date = :modification_date, hidden = :hidden
|
||||||
WHERE
|
WHERE
|
||||||
id = :id
|
id = :id
|
||||||
"""
|
"""
|
||||||
data = conversion.tema_to_row(tema)
|
data = conversion.tema_to_row(tema)
|
||||||
|
with get_connection(con) as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query, data)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def update_ngrams(tema: model.Tema, con: Connection | None = None):
|
||||||
|
query = """
|
||||||
|
UPDATE temes
|
||||||
|
SET
|
||||||
|
ngrams = :ngrams
|
||||||
|
WHERE
|
||||||
|
id = :id
|
||||||
|
"""
|
||||||
|
data = dict(id=tema.id, ngrams=json.dumps(tema.ngrams()))
|
||||||
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)
|
||||||
|
|||||||
@@ -1,239 +0,0 @@
|
|||||||
from fastapi import HTTPException, Request
|
|
||||||
from folkugat_web.model import temes as model
|
|
||||||
from folkugat_web.services.temes import query as temes_q
|
|
||||||
from folkugat_web.services.temes.links import guess_link_type
|
|
||||||
from folkugat_web.templates import templates
|
|
||||||
|
|
||||||
|
|
||||||
def title(request: Request, logged_in: bool, tema: model.Tema | None = None, tema_id: int | None = None):
|
|
||||||
if tema is None:
|
|
||||||
if tema_id is None:
|
|
||||||
raise ValueError("Either 'tema' or 'tema_id' must be given!")
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id)
|
|
||||||
if not tema:
|
|
||||||
raise HTTPException(status_code=404, detail="Could not find tune")
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/title.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema": tema,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def title_editor(request: Request, logged_in: bool, 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")
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/editor/title.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema": tema,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def lyric(request: Request, logged_in: bool, tema_id: int, lyric_id: int):
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id)
|
|
||||||
if not tema:
|
|
||||||
raise HTTPException(status_code=404, detail="Could not find tune")
|
|
||||||
lyric = tema.lyrics.get(lyric_id)
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/lyric.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema": tema,
|
|
||||||
"lyric_id": lyric_id,
|
|
||||||
"lyric": lyric,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def lyric_editor(request: Request, logged_in: bool, tema_id: int, lyric_id: int):
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id)
|
|
||||||
if not tema:
|
|
||||||
raise HTTPException(status_code=404, detail="Could not find tune")
|
|
||||||
lyric = tema.lyrics.get(lyric_id)
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/editor/lyric.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema": tema,
|
|
||||||
"lyric_id": lyric_id,
|
|
||||||
"lyric": lyric,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def link(request: Request, logged_in: bool, tema_id: int, link_id: int):
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id)
|
|
||||||
if not tema:
|
|
||||||
raise HTTPException(status_code=404, detail="Could not find tune")
|
|
||||||
link = tema.links.get(link_id)
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/link.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema": tema,
|
|
||||||
"link_id": link_id,
|
|
||||||
"link": link,
|
|
||||||
"LinkType": model.LinkType,
|
|
||||||
"ContentType": model.ContentType,
|
|
||||||
},
|
|
||||||
headers={
|
|
||||||
"HX-Trigger": f"reload-tema-{tema_id}-score"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def link_editor(request: Request, logged_in: bool, tema_id: int, link_id: int):
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id)
|
|
||||||
if not tema:
|
|
||||||
raise HTTPException(status_code=404, detail="Could not find tune")
|
|
||||||
link = tema.links.get(link_id)
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/editor/link.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema": tema,
|
|
||||||
"link_id": link_id,
|
|
||||||
"link": link,
|
|
||||||
"LinkType": model.LinkType,
|
|
||||||
"ContentType": model.ContentType,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def link_editor_url(request: Request, logged_in: bool, tema_id: int, link_id: int):
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id)
|
|
||||||
if not tema:
|
|
||||||
raise HTTPException(status_code=404, detail="Could not find tune")
|
|
||||||
link = tema.links.get(link_id)
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/editor/link_url.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema": tema,
|
|
||||||
"link_id": link_id,
|
|
||||||
"link": link,
|
|
||||||
"LinkType": model.LinkType,
|
|
||||||
"ContentType": model.ContentType,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def link_editor_file(request: Request, logged_in: bool, tema_id: int, link_id: int):
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id)
|
|
||||||
if not tema:
|
|
||||||
raise HTTPException(status_code=404, detail="Could not find tune")
|
|
||||||
link = tema.links.get(link_id)
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/editor/link_file.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema": tema,
|
|
||||||
"link_id": link_id,
|
|
||||||
"link": link,
|
|
||||||
"LinkType": model.LinkType,
|
|
||||||
"ContentType": model.ContentType,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def link_icon(request: Request, logged_in: bool, tema_id: int, link_id: int, url: str,
|
|
||||||
content_type: model.ContentType):
|
|
||||||
link = model.Link(
|
|
||||||
id=link_id,
|
|
||||||
content_type=content_type,
|
|
||||||
link_type=guess_link_type(url),
|
|
||||||
url=url,
|
|
||||||
)
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/link_icon.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema_id": tema_id,
|
|
||||||
"link_id": link_id,
|
|
||||||
"link": link,
|
|
||||||
"LinkType": model.LinkType,
|
|
||||||
"ContentType": model.ContentType,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def score(request: Request, logged_in: bool, 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")
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/score.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema": tema,
|
|
||||||
"LinkType": model.LinkType,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def property_(request: Request, logged_in: bool, tema_id: int, property_id: int):
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id)
|
|
||||||
if not tema:
|
|
||||||
raise HTTPException(status_code=404, detail="Could not find tune")
|
|
||||||
prop = tema.properties.get(property_id)
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/property.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema": tema,
|
|
||||||
"property_id": property_id,
|
|
||||||
"property": prop,
|
|
||||||
"PropertyField": model.PropertyField,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def property_editor(request: Request, logged_in: bool, tema_id: int, property_id: int):
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id)
|
|
||||||
if not tema:
|
|
||||||
raise HTTPException(status_code=404, detail="Could not find tune")
|
|
||||||
prop = tema.properties.get(property_id)
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/editor/property.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema": tema,
|
|
||||||
"property_id": property_id,
|
|
||||||
"property": prop,
|
|
||||||
"PropertyField": model.PropertyField,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def visibility(request: Request, logged_in: bool, tema: model.Tema):
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"fragments/tema/visibility.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"logged_in": logged_in,
|
|
||||||
"tema": tema,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
84
folkugat_web/fragments/tema/__init__.py
Normal file
84
folkugat_web/fragments/tema/__init__.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
from fastapi import HTTPException, Request
|
||||||
|
from folkugat_web.model import temes as model
|
||||||
|
from folkugat_web.services.temes import query as temes_q
|
||||||
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
|
|
||||||
|
def title(request: Request, logged_in: bool, tema: model.Tema | None = None, tema_id: int | None = None):
|
||||||
|
if tema is None:
|
||||||
|
if tema_id is None:
|
||||||
|
raise ValueError("Either 'tema' or 'tema_id' must be given!")
|
||||||
|
tema = temes_q.get_tema_by_id(tema_id)
|
||||||
|
if not tema:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find tune")
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/title.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"tema": tema,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def title_editor(request: Request, logged_in: bool, 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")
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/editor/title.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"tema": tema,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def property_(request: Request, logged_in: bool, tema_id: int, property_id: int):
|
||||||
|
tema = temes_q.get_tema_by_id(tema_id)
|
||||||
|
if not tema:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find tune")
|
||||||
|
prop = tema.properties.get(property_id)
|
||||||
|
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/property.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"tema": tema,
|
||||||
|
"property_id": property_id,
|
||||||
|
"property": prop,
|
||||||
|
"PropertyField": model.PropertyField,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def property_editor(request: Request, logged_in: bool, tema_id: int, property_id: int):
|
||||||
|
tema = temes_q.get_tema_by_id(tema_id)
|
||||||
|
if not tema:
|
||||||
|
raise HTTPException(status_code=404, detail="Could not find tune")
|
||||||
|
prop = tema.properties.get(property_id)
|
||||||
|
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/editor/property.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"tema": tema,
|
||||||
|
"property_id": property_id,
|
||||||
|
"property": prop,
|
||||||
|
"PropertyField": model.PropertyField,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def visibility(request: Request, logged_in: bool, tema: model.Tema):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/visibility.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"tema": tema,
|
||||||
|
}
|
||||||
|
)
|
||||||
88
folkugat_web/fragments/tema/links.py
Normal file
88
folkugat_web/fragments/tema/links.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
from fastapi import HTTPException, Request
|
||||||
|
from folkugat_web.model import temes as model
|
||||||
|
from folkugat_web.services.temes import query as temes_q
|
||||||
|
from folkugat_web.services.temes.links import guess_link_type
|
||||||
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
|
|
||||||
|
def link(request: Request, logged_in: bool, link: model.Link):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/link.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"link": link,
|
||||||
|
"LinkType": model.LinkType,
|
||||||
|
"ContentType": model.ContentType,
|
||||||
|
},
|
||||||
|
headers={
|
||||||
|
"HX-Trigger": f"reload-tema-{link.tema_id}-score"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def link_editor(request: Request, logged_in: bool, link: model.Link):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/editor/link.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"link": link,
|
||||||
|
"LinkType": model.LinkType,
|
||||||
|
"ContentType": model.ContentType,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def link_editor_url(request: Request, logged_in: bool, link: model.Link):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/editor/link_url.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"link": link,
|
||||||
|
"LinkType": model.LinkType,
|
||||||
|
"ContentType": model.ContentType,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def link_editor_file(request: Request, logged_in: bool, link: model.Link):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/editor/link_file.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"link": link,
|
||||||
|
"LinkType": model.LinkType,
|
||||||
|
"ContentType": model.ContentType,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def link_icon(request: Request, logged_in: bool, link: model.Link):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/link_icon.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"link": link,
|
||||||
|
"LinkType": model.LinkType,
|
||||||
|
"ContentType": model.ContentType,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def score(request: Request, logged_in: bool, 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")
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/score.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"tema": tema,
|
||||||
|
"LinkType": model.LinkType,
|
||||||
|
}
|
||||||
|
)
|
||||||
25
folkugat_web/fragments/tema/lyrics.py
Normal file
25
folkugat_web/fragments/tema/lyrics.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from fastapi import Request
|
||||||
|
from folkugat_web.model import temes as model
|
||||||
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
|
|
||||||
|
def lyric(request: Request, logged_in: bool, lyric: model.Lyrics):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/lyric.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"lyric": lyric,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def lyric_editor(request: Request, logged_in: bool, lyric: model.Lyrics):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/editor/lyric.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"lyric": lyric,
|
||||||
|
}
|
||||||
|
)
|
||||||
27
folkugat_web/fragments/tema/properties.py
Normal file
27
folkugat_web/fragments/tema/properties.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from fastapi import Request
|
||||||
|
from folkugat_web.model import temes as model
|
||||||
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
|
|
||||||
|
def property_(request: Request, logged_in: bool, property: model.Property):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/property.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"property": property,
|
||||||
|
"PropertyField": model.PropertyField,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def property_editor(request: Request, logged_in: bool, property: model.Property):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"fragments/tema/editor/property.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"logged_in": logged_in,
|
||||||
|
"property": property,
|
||||||
|
"PropertyField": model.PropertyField,
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
from fastapi import HTTPException, Request
|
from fastapi import Request
|
||||||
from folkugat_web.model import temes as model
|
from folkugat_web.model import temes as model
|
||||||
from folkugat_web.model.pagines import Pages
|
from folkugat_web.model.pagines import Pages
|
||||||
from folkugat_web.services import sessions as sessions_service
|
from folkugat_web.services import sessions as sessions_service
|
||||||
|
from folkugat_web.services.temes import links as links_service
|
||||||
from folkugat_web.services.temes import query as temes_q
|
from folkugat_web.services.temes import query as temes_q
|
||||||
from folkugat_web.services.temes import search as temes_s
|
from folkugat_web.services.temes import search as temes_s
|
||||||
from folkugat_web.templates import templates
|
from folkugat_web.templates import templates
|
||||||
|
from folkugat_web.utils import FnChain
|
||||||
|
|
||||||
|
|
||||||
def temes_pagina(request: Request, logged_in: bool, query: str):
|
def temes_pagina(request: Request, logged_in: bool, query: str):
|
||||||
@@ -35,7 +37,12 @@ def temes_busca(request: Request, logged_in: bool, query: str, offset: int = 0,
|
|||||||
if offset > 0:
|
if offset > 0:
|
||||||
prev_offset = max(offset - limit, 0)
|
prev_offset = max(offset - limit, 0)
|
||||||
|
|
||||||
temes = temes_q.temes_compute_stats(temes)
|
temes = (
|
||||||
|
FnChain.transform(temes) |
|
||||||
|
temes_q.temes_compute_stats |
|
||||||
|
links_service.add_links_to_temes |
|
||||||
|
list
|
||||||
|
).result()
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"fragments/temes/results.html",
|
"fragments/temes/results.html",
|
||||||
@@ -52,11 +59,7 @@ def temes_busca(request: Request, logged_in: bool, query: str, offset: int = 0,
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def tema(request: Request, tema_id: int, logged_in: bool):
|
def tema(request: Request, logged_in: bool, tema: model.Tema):
|
||||||
tema = temes_q.get_tema_by_id(tema_id)
|
|
||||||
if not tema:
|
|
||||||
raise HTTPException(status_code=404, detail="Could not find tune")
|
|
||||||
tema = temes_q.tema_compute_stats(tema)
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"fragments/tema/pagina.html",
|
"fragments/tema/pagina.html",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
from ._base import IndexedList
|
|
||||||
|
|
||||||
__all__ = ["IndexedList"]
|
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import dataclasses
|
|
||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
from typing_extensions import override
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class WithId:
|
|
||||||
id: int | None
|
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar("T", bound=WithId)
|
|
||||||
|
|
||||||
|
|
||||||
class IndexedList(list[T]):
|
|
||||||
@override
|
|
||||||
def append(self, _item: T) -> None:
|
|
||||||
if _item.id is None:
|
|
||||||
_item.id = max((i.id or 0 for i in self), default=0) + 1
|
|
||||||
return super().append(_item)
|
|
||||||
|
|
||||||
def find_idx(self, _id: int) -> int:
|
|
||||||
try:
|
|
||||||
i, _ = next(filter(lambda it: it[1].id == _id, enumerate(self)))
|
|
||||||
except StopIteration:
|
|
||||||
raise ValueError(f"Could not find item with id {_id}")
|
|
||||||
return i
|
|
||||||
|
|
||||||
def get(self, _id: int) -> T:
|
|
||||||
i = self.find_idx(_id)
|
|
||||||
return self.__getitem__(i)
|
|
||||||
|
|
||||||
def delete(self, _id: int) -> None:
|
|
||||||
i = self.find_idx(_id)
|
|
||||||
return super().__delitem__(i)
|
|
||||||
|
|
||||||
def replace(self, _id: int, _obj: T) -> None:
|
|
||||||
i = self.find_idx(_id)
|
|
||||||
super().__setitem__(i, _obj)
|
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import datetime
|
import datetime
|
||||||
import enum
|
import enum
|
||||||
from typing import Self
|
|
||||||
|
|
||||||
from folkugat_web.model.search import NGrams
|
from folkugat_web.model.search import NGrams
|
||||||
from folkugat_web.model.sessions import Session
|
from folkugat_web.model.sessions import Session
|
||||||
from folkugat_web.services import ngrams
|
from folkugat_web.services import ngrams
|
||||||
|
|
||||||
from ._base import IndexedList, WithId
|
|
||||||
|
|
||||||
|
|
||||||
class ContentType(enum.Enum):
|
class ContentType(enum.Enum):
|
||||||
PARTITURA = "partitura"
|
PARTITURA = "partitura"
|
||||||
@@ -26,31 +23,14 @@ class LinkType(enum.Enum):
|
|||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Link(WithId):
|
class Link:
|
||||||
|
id: int | None
|
||||||
|
tema_id: int
|
||||||
content_type: ContentType
|
content_type: ContentType
|
||||||
link_type: LinkType | None
|
link_type: LinkType | None
|
||||||
url: str
|
url: str
|
||||||
title: str = ""
|
title: str = ""
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return dict(
|
|
||||||
id=self.id,
|
|
||||||
content_type=self.content_type.value,
|
|
||||||
link_type=self.link_type.value if self.link_type else None,
|
|
||||||
url=self.url,
|
|
||||||
title=self.title,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, d):
|
|
||||||
return cls(
|
|
||||||
id=d["id"],
|
|
||||||
content_type=ContentType(d["content_type"]),
|
|
||||||
link_type=LinkType(d["link_type"]) if d["link_type"] else None,
|
|
||||||
url=d["url"],
|
|
||||||
title=d["title"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PropertyField(enum.Enum):
|
class PropertyField(enum.Enum):
|
||||||
AUTOR = "autor"
|
AUTOR = "autor"
|
||||||
@@ -60,46 +40,20 @@ class PropertyField(enum.Enum):
|
|||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Property(WithId):
|
class Property:
|
||||||
|
id: int | None
|
||||||
|
tema_id: int
|
||||||
field: PropertyField
|
field: PropertyField
|
||||||
value: str
|
value: str
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return dict(
|
|
||||||
id=self.id,
|
|
||||||
field=self.field.value,
|
|
||||||
value=self.value,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, d):
|
|
||||||
return cls(
|
|
||||||
id=d["id"],
|
|
||||||
field=PropertyField(d["field"]),
|
|
||||||
value=d["value"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Lyrics(WithId):
|
class Lyrics:
|
||||||
|
id: int | None
|
||||||
|
tema_id: int
|
||||||
title: str
|
title: str
|
||||||
content: str
|
content: str
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return dict(
|
|
||||||
id=self.id,
|
|
||||||
title=self.title,
|
|
||||||
content=self.content,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, d) -> Self:
|
|
||||||
return cls(
|
|
||||||
id=d["id"],
|
|
||||||
title=d["title"],
|
|
||||||
content=d["content"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Stats:
|
class Stats:
|
||||||
@@ -112,12 +66,11 @@ class Tema:
|
|||||||
id: int | None = None
|
id: int | None = None
|
||||||
# Info
|
# Info
|
||||||
title: str = ""
|
title: str = ""
|
||||||
properties: IndexedList[Property] = dataclasses.field(default_factory=IndexedList)
|
properties: list[Property] = dataclasses.field(default_factory=list)
|
||||||
links: IndexedList[Link] = dataclasses.field(default_factory=IndexedList)
|
links: list[Link] = dataclasses.field(default_factory=list)
|
||||||
lyrics: IndexedList[Lyrics] = dataclasses.field(default_factory=IndexedList)
|
lyrics: list[Lyrics] = dataclasses.field(default_factory=list)
|
||||||
# Search related
|
# Search related
|
||||||
alternatives: list[str] = dataclasses.field(default_factory=list)
|
alternatives: list[str] = dataclasses.field(default_factory=list)
|
||||||
ngrams: NGrams = dataclasses.field(default_factory=dict)
|
|
||||||
hidden: bool = True
|
hidden: bool = True
|
||||||
# Other info
|
# Other info
|
||||||
modification_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
|
modification_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
|
||||||
@@ -125,12 +78,8 @@ class Tema:
|
|||||||
# Stats
|
# Stats
|
||||||
stats: Stats | None = None
|
stats: Stats | None = None
|
||||||
|
|
||||||
def compute_ngrams(self):
|
def ngrams(self) -> NGrams:
|
||||||
self.ngrams = ngrams.get_text_ngrams(self.title, *self.alternatives)
|
return ngrams.get_text_ngrams(self.title, *self.alternatives)
|
||||||
|
|
||||||
def with_ngrams(self):
|
|
||||||
self.compute_ngrams()
|
|
||||||
return self
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _is_score(link: Link) -> bool:
|
def _is_score(link: Link) -> bool:
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ async def store_file(tema_id: int, upload_file: UploadFile) -> str:
|
|||||||
filepath = filedir / filename
|
filepath = filedir / filename
|
||||||
|
|
||||||
with open(filepath, "wb") as f:
|
with open(filepath, "wb") as f:
|
||||||
f.write(await upload_file.read())
|
_ = f.write(await upload_file.read())
|
||||||
|
|
||||||
return get_db_file_path(filepath)
|
return get_db_file_path(filepath)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import re
|
import re
|
||||||
from typing import Optional
|
from collections.abc import Iterable, Iterator
|
||||||
|
|
||||||
|
from folkugat_web.dal.sql.temes import links as links_dal
|
||||||
from folkugat_web.model import temes as model
|
from folkugat_web.model import temes as model
|
||||||
|
|
||||||
IMAGE_FORMATS_RE = "|".join([
|
IMAGE_FORMATS_RE = "|".join([
|
||||||
@@ -30,3 +31,29 @@ def guess_link_type(url: str) -> model.LinkType | None:
|
|||||||
if regex.match(url):
|
if regex.match(url):
|
||||||
return link_type
|
return link_type
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def add_links_to_tema(tema: model.Tema) -> model.Tema:
|
||||||
|
if tema.id is not None:
|
||||||
|
tema.links = list(links_dal.get_links(tema_id=tema.id))
|
||||||
|
return tema
|
||||||
|
|
||||||
|
|
||||||
|
def add_links_to_temes(temes: Iterable[model.Tema]) -> Iterator[model.Tema]:
|
||||||
|
return map(add_links_to_tema, temes)
|
||||||
|
|
||||||
|
|
||||||
|
def get_link_by_id(link_id: int, tema_id: int | None = None) -> model.Link | None:
|
||||||
|
return next(iter(links_dal.get_links(link_id=link_id, tema_id=tema_id)), None)
|
||||||
|
|
||||||
|
|
||||||
|
def update_link(link: model.Link):
|
||||||
|
links_dal.update_link(link=link)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_link(link_id: int, tema_id: int | None = None):
|
||||||
|
links_dal.delete_link(link_id=link_id, tema_id=tema_id)
|
||||||
|
|
||||||
|
|
||||||
|
def create_link(tema_id: int) -> model.Link:
|
||||||
|
return links_dal.create_link(tema_id=tema_id)
|
||||||
|
|||||||
30
folkugat_web/services/temes/lyrics.py
Normal file
30
folkugat_web/services/temes/lyrics.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from collections.abc import Iterable, Iterator
|
||||||
|
|
||||||
|
from folkugat_web.dal.sql.temes import lyrics as lyrics_dal
|
||||||
|
from folkugat_web.model import temes as model
|
||||||
|
|
||||||
|
|
||||||
|
def add_lyrics_to_tema(tema: model.Tema) -> model.Tema:
|
||||||
|
if tema.id is not None:
|
||||||
|
tema.lyrics = list(lyrics_dal.get_lyrics(tema_id=tema.id))
|
||||||
|
return tema
|
||||||
|
|
||||||
|
|
||||||
|
def add_lyrics_to_temes(temes: Iterable[model.Tema]) -> Iterator[model.Tema]:
|
||||||
|
return map(add_lyrics_to_tema, temes)
|
||||||
|
|
||||||
|
|
||||||
|
def get_lyric_by_id(lyric_id: int, tema_id: int | None = None) -> model.Lyrics | None:
|
||||||
|
return next(iter(lyrics_dal.get_lyrics(lyric_id=lyric_id, tema_id=tema_id)), None)
|
||||||
|
|
||||||
|
|
||||||
|
def update_lyric(lyric: model.Lyrics):
|
||||||
|
lyrics_dal.update_lyric(lyric=lyric)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_lyric(lyric_id: int, tema_id: int | None = None):
|
||||||
|
lyrics_dal.delete_lyric(lyric_id=lyric_id, tema_id=tema_id)
|
||||||
|
|
||||||
|
|
||||||
|
def create_lyric(tema_id: int) -> model.Lyrics:
|
||||||
|
return lyrics_dal.create_lyric(tema_id=tema_id)
|
||||||
30
folkugat_web/services/temes/properties.py
Normal file
30
folkugat_web/services/temes/properties.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from collections.abc import Iterable, Iterator
|
||||||
|
|
||||||
|
from folkugat_web.dal.sql.temes import properties as properties_dal
|
||||||
|
from folkugat_web.model import temes as model
|
||||||
|
|
||||||
|
|
||||||
|
def add_properties_to_tema(tema: model.Tema) -> model.Tema:
|
||||||
|
if tema.id is not None:
|
||||||
|
tema.properties = list(properties_dal.get_properties(tema_id=tema.id))
|
||||||
|
return tema
|
||||||
|
|
||||||
|
|
||||||
|
def add_properties_to_temes(temes: Iterable[model.Tema]) -> Iterator[model.Tema]:
|
||||||
|
return map(add_properties_to_tema, temes)
|
||||||
|
|
||||||
|
|
||||||
|
def get_property_by_id(property_id: int, tema_id: int | None = None) -> model.Property | None:
|
||||||
|
return next(iter(properties_dal.get_properties(property_id=property_id, tema_id=tema_id)), None)
|
||||||
|
|
||||||
|
|
||||||
|
def update_property(property: model.Property):
|
||||||
|
properties_dal.update_property(property=property)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_property(property_id: int, tema_id: int | None = None):
|
||||||
|
properties_dal.delete_property(property_id=property_id, tema_id=tema_id)
|
||||||
|
|
||||||
|
|
||||||
|
def create_property(tema_id: int) -> model.Property:
|
||||||
|
return properties_dal.create_property(tema_id=tema_id)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import functools
|
|
||||||
import time
|
import time
|
||||||
import typing
|
from collections.abc import Iterable, Iterator
|
||||||
from collections.abc import Callable, Iterable
|
from sqlite3 import Connection
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
import Levenshtein
|
import Levenshtein
|
||||||
from folkugat_web.config import search as config
|
from folkugat_web.config import search as config
|
||||||
@@ -10,6 +10,7 @@ from folkugat_web.dal.sql.temes import query as temes_q
|
|||||||
from folkugat_web.log import logger
|
from folkugat_web.log import logger
|
||||||
from folkugat_web.model import search as search_model
|
from folkugat_web.model import search as search_model
|
||||||
from folkugat_web.model import temes as model
|
from folkugat_web.model import temes as model
|
||||||
|
from folkugat_web.utils import FnChain
|
||||||
|
|
||||||
|
|
||||||
def get_query_word_similarity(query_word: str, text_ngrams: search_model.NGrams) -> search_model.SearchMatch:
|
def get_query_word_similarity(query_word: str, text_ngrams: search_model.NGrams) -> search_model.SearchMatch:
|
||||||
@@ -33,40 +34,66 @@ def get_query_similarity(query: str, ngrams: search_model.NGrams) -> search_mode
|
|||||||
return search_model.SearchMatch.combine_matches(word_matches)
|
return search_model.SearchMatch.combine_matches(word_matches)
|
||||||
|
|
||||||
|
|
||||||
def build_result(query: str, entry: tuple[int, search_model.NGrams]) -> search_model.QueryResult:
|
def _build_results_fn(query: str) -> Callable[[Iterable[tuple[int, search_model.NGrams]]],
|
||||||
if len(query) == 0:
|
Iterator[search_model.QueryResult]]:
|
||||||
|
def build_result(entry: tuple[int, search_model.NGrams]) -> search_model.QueryResult:
|
||||||
|
if len(query) == 0:
|
||||||
|
return search_model.QueryResult(
|
||||||
|
id=entry[0],
|
||||||
|
distance=0,
|
||||||
|
ngram="",
|
||||||
|
)
|
||||||
|
match = get_query_similarity(query, entry[1])
|
||||||
return search_model.QueryResult(
|
return search_model.QueryResult(
|
||||||
id=entry[0],
|
id=entry[0],
|
||||||
distance=0,
|
distance=match.distance,
|
||||||
ngram="",
|
ngram=match.ngram,
|
||||||
)
|
)
|
||||||
match = get_query_similarity(query, entry[1])
|
|
||||||
return search_model.QueryResult(
|
def build_results(entries: Iterable[tuple[int, search_model.NGrams]]) -> Iterator[search_model.QueryResult]:
|
||||||
id=entry[0],
|
return map(build_result, entries)
|
||||||
distance=match.distance,
|
|
||||||
ngram=match.ngram,
|
return build_results
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
T = typing.TypeVar("T")
|
def _filter_distance(qrs: Iterable[search_model.QueryResult]) -> Iterator[search_model.QueryResult]:
|
||||||
|
return filter(lambda qr: qr.distance <= config.SEARCH_DISTANCE_THRESHOLD, qrs)
|
||||||
|
|
||||||
|
|
||||||
@typing.no_type_check
|
def _sort_by_distance(qrs: Iterable[search_model.QueryResult]) -> list[search_model.QueryResult]:
|
||||||
def _thread(it: Iterable[T], *funcs: Callable[[Iterable], Iterable]) -> Iterable:
|
return sorted(qrs, key=lambda qr: qr.distance)
|
||||||
return functools.reduce(lambda i, fn: fn(i), funcs, it)
|
|
||||||
|
|
||||||
|
def _query_results_to_temes(con: Connection) -> Callable[[Iterable[search_model.QueryResult]], Iterator[model.Tema]]:
|
||||||
|
def fetch_temes(qrs: Iterable[search_model.QueryResult]) -> Iterator[model.Tema]:
|
||||||
|
return filter(None, map(lambda qr: temes_q.get_tema_by_id(tema_id=qr.id, con=con), qrs))
|
||||||
|
return fetch_temes
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_hidden(hidden: bool) -> Callable[[Iterable[model.Tema]], Iterator[model.Tema]]:
|
||||||
|
def filter_hidden(temes: Iterable[model.Tema]) -> Iterator[model.Tema]:
|
||||||
|
return filter(lambda t: hidden or not t.hidden, temes)
|
||||||
|
return filter_hidden
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_limit_offset(limit: int, offset: int) -> Callable[[Iterable[model.Tema]], list[model.Tema]]:
|
||||||
|
def apply_limit_offset(temes: Iterable[model.Tema]) -> list[model.Tema]:
|
||||||
|
return list(temes)[offset:offset + limit]
|
||||||
|
return apply_limit_offset
|
||||||
|
|
||||||
|
|
||||||
def busca_temes(query: str, hidden: bool = False, limit: int = 10, offset: int = 0) -> list[model.Tema]:
|
def busca_temes(query: str, hidden: bool = False, limit: int = 10, offset: int = 0) -> list[model.Tema]:
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
|
|
||||||
with get_connection() as con:
|
with get_connection() as con:
|
||||||
result = _thread(
|
result = (
|
||||||
temes_q.get_tema_id_to_ngrams(con).items(),
|
FnChain.transform(temes_q.get_tema_id_to_ngrams(con).items()) |
|
||||||
lambda tema_id_to_ngrams: (build_result(query, entry) for entry in tema_id_to_ngrams),
|
_build_results_fn(query) |
|
||||||
lambda results: filter(lambda qr: qr.distance <= config.SEARCH_DISTANCE_THRESHOLD, results),
|
_filter_distance |
|
||||||
lambda results: sorted(results, key=lambda qr: qr.distance),
|
_sort_by_distance |
|
||||||
lambda results: filter(None, map(lambda qr: temes_q.get_tema_by_id(qr.id, con), results)),
|
_query_results_to_temes(con) |
|
||||||
lambda results: filter(lambda t: hidden or not t.hidden, results),
|
_filter_hidden(hidden) |
|
||||||
)
|
_apply_limit_offset(limit=limit, offset=offset)
|
||||||
result = list(result)[offset:offset + limit]
|
).result()
|
||||||
logger.info(f"Search time: { int((time.time() - t0) * 1000) } ms")
|
logger.info(f"Search time: { int((time.time() - t0) * 1000) } ms")
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ from folkugat_web.model import temes as model
|
|||||||
|
|
||||||
|
|
||||||
def create_tema(title: str = "") -> model.Tema:
|
def create_tema(title: str = "") -> model.Tema:
|
||||||
new_tema = model.Tema(title=title, hidden=False).with_ngrams()
|
with get_connection() as con:
|
||||||
return temes_w.insert_tema(tema=new_tema)
|
new_tema = temes_w.insert_tema(tema=model.Tema(title=title, hidden=False), con=con)
|
||||||
|
temes_w.update_ngrams(tema=new_tema, con=con)
|
||||||
|
return new_tema
|
||||||
|
|
||||||
|
|
||||||
def delete_tema(tema_id: int) -> None:
|
def delete_tema(tema_id: int) -> None:
|
||||||
@@ -21,131 +23,13 @@ def update_title(tema_id: int, title: str) -> model.Tema:
|
|||||||
if tema is None:
|
if tema is None:
|
||||||
raise ValueError(f"No tune found with tema_id = {tema_id}!")
|
raise ValueError(f"No tune found with tema_id = {tema_id}!")
|
||||||
|
|
||||||
new_tema = dataclasses.replace(tema, title=title).with_ngrams()
|
new_tema = dataclasses.replace(tema, title=title)
|
||||||
temes_w.update_tema(tema=new_tema, con=con)
|
temes_w.update_tema(tema=new_tema, con=con)
|
||||||
|
temes_w.update_ngrams(tema=new_tema, con=con)
|
||||||
|
|
||||||
return new_tema
|
return new_tema
|
||||||
|
|
||||||
|
|
||||||
def update_lyric(tema_id: int, lyric_id: int, lyric: model.Lyrics) -> model.Tema:
|
|
||||||
with get_connection() as con:
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
|
|
||||||
if tema is None:
|
|
||||||
raise ValueError(f"No tune found with tema_id = {tema_id}!")
|
|
||||||
|
|
||||||
tema.lyrics.replace(lyric_id, lyric)
|
|
||||||
temes_w.update_tema(tema=tema, con=con)
|
|
||||||
|
|
||||||
return tema
|
|
||||||
|
|
||||||
|
|
||||||
def delete_lyric(tema_id: int, lyric_id: int) -> model.Tema:
|
|
||||||
with get_connection() as con:
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
|
|
||||||
if tema is None:
|
|
||||||
raise ValueError(f"No tune found with tema_id = {tema_id}!")
|
|
||||||
|
|
||||||
tema.lyrics.delete(lyric_id)
|
|
||||||
temes_w.update_tema(tema=tema, con=con)
|
|
||||||
|
|
||||||
return tema
|
|
||||||
|
|
||||||
|
|
||||||
def add_lyric(tema_id: int) -> model.Tema:
|
|
||||||
with get_connection() as con:
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
|
|
||||||
if tema is None:
|
|
||||||
raise ValueError(f"No tune found with tema_id = {tema_id}!")
|
|
||||||
tema.lyrics.append(model.Lyrics(
|
|
||||||
id=None,
|
|
||||||
title=tema.title,
|
|
||||||
content="",
|
|
||||||
))
|
|
||||||
temes_w.update_tema(tema=tema, con=con)
|
|
||||||
|
|
||||||
return tema
|
|
||||||
|
|
||||||
|
|
||||||
def update_link(tema_id: int, link_id: int, link: model.Link) -> model.Tema:
|
|
||||||
with get_connection() as con:
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
|
|
||||||
if tema is None:
|
|
||||||
raise ValueError(f"No tune found with tema_id = {tema_id}!")
|
|
||||||
|
|
||||||
tema.links.replace(link_id, link)
|
|
||||||
temes_w.update_tema(tema=tema, con=con)
|
|
||||||
|
|
||||||
return tema
|
|
||||||
|
|
||||||
|
|
||||||
def delete_link(tema_id: int, link_id: int) -> model.Tema:
|
|
||||||
with get_connection() as con:
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
|
|
||||||
if tema is None:
|
|
||||||
raise ValueError(f"No tune found with tema_id = {tema_id}!")
|
|
||||||
|
|
||||||
tema.links.delete(link_id)
|
|
||||||
temes_w.update_tema(tema=tema, con=con)
|
|
||||||
|
|
||||||
return tema
|
|
||||||
|
|
||||||
|
|
||||||
def add_link(tema_id: int) -> model.Tema:
|
|
||||||
with get_connection() as con:
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
|
|
||||||
if tema is None:
|
|
||||||
raise ValueError(f"No tune found with tema_id = {tema_id}!")
|
|
||||||
tema.links.append(model.Link(
|
|
||||||
id=None,
|
|
||||||
title="",
|
|
||||||
url="",
|
|
||||||
content_type=model.ContentType.OTHER,
|
|
||||||
link_type=None,
|
|
||||||
))
|
|
||||||
temes_w.update_tema(tema=tema, con=con)
|
|
||||||
|
|
||||||
return tema
|
|
||||||
|
|
||||||
|
|
||||||
def update_property(tema_id: int, property_id: int, prop: model.Property) -> model.Tema:
|
|
||||||
with get_connection() as con:
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
|
|
||||||
if tema is None:
|
|
||||||
raise ValueError(f"No tune found with tema_id = {tema_id}!")
|
|
||||||
|
|
||||||
tema.properties.replace(property_id, prop)
|
|
||||||
temes_w.update_tema(tema=tema, con=con)
|
|
||||||
|
|
||||||
return tema
|
|
||||||
|
|
||||||
|
|
||||||
def delete_property(tema_id: int, property_id: int) -> model.Tema:
|
|
||||||
with get_connection() as con:
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
|
|
||||||
if tema is None:
|
|
||||||
raise ValueError(f"No tune found with tema_id = {tema_id}!")
|
|
||||||
|
|
||||||
tema.properties.delete(property_id)
|
|
||||||
temes_w.update_tema(tema=tema, con=con)
|
|
||||||
|
|
||||||
return tema
|
|
||||||
|
|
||||||
|
|
||||||
def add_property(tema_id: int) -> model.Tema:
|
|
||||||
with get_connection() as con:
|
|
||||||
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
|
|
||||||
if tema is None:
|
|
||||||
raise ValueError(f"No tune found with tema_id = {tema_id}!")
|
|
||||||
tema.properties.append(model.Property(
|
|
||||||
id=None,
|
|
||||||
field=model.PropertyField.AUTOR,
|
|
||||||
value="",
|
|
||||||
))
|
|
||||||
temes_w.update_tema(tema=tema, con=con)
|
|
||||||
|
|
||||||
return tema
|
|
||||||
|
|
||||||
|
|
||||||
def set_visibility(tema_id: int, hidden: bool) -> model.Tema:
|
def set_visibility(tema_id: int, hidden: bool) -> model.Tema:
|
||||||
with get_connection() as con:
|
with get_connection() as con:
|
||||||
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
|
tema = temes_q.get_tema_by_id(tema_id=tema_id, con=con)
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ from typing import TypeVar
|
|||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
ListOrValue = T | list[T]
|
ListOrValue = T | list[T]
|
||||||
OptionalListOrValue = ListOrValue[T] | None
|
OptionalListOrValue = T | list[T] | None
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
from collections.abc import Callable, Iterable, Iterator
|
from collections.abc import Callable, Iterable, Iterator
|
||||||
from typing import Protocol, Self, TypeVar
|
from typing import Generic, Protocol, Self, TypeVar
|
||||||
|
|
||||||
|
|
||||||
class SupportsLessThan(Protocol):
|
class SupportsLessThan(Protocol):
|
||||||
@@ -20,3 +22,28 @@ def groupby(
|
|||||||
) -> Iterator[tuple[KeyT, GroupT]]:
|
) -> Iterator[tuple[KeyT, GroupT]]:
|
||||||
for k, g in itertools.groupby(sorted(it, key=key_fn), key=key_fn):
|
for k, g in itertools.groupby(sorted(it, key=key_fn), key=key_fn):
|
||||||
yield k, group_fn(g)
|
yield k, group_fn(g)
|
||||||
|
|
||||||
|
|
||||||
|
U = TypeVar("U")
|
||||||
|
V = TypeVar("V")
|
||||||
|
|
||||||
|
|
||||||
|
def map_none(fn: Callable[[U], V], value: U | None) -> V | None:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return fn(value)
|
||||||
|
|
||||||
|
|
||||||
|
class FnChain(Generic[U]):
|
||||||
|
def __init__(self, fn: Callable[[], U], /):
|
||||||
|
self._fn: Callable[[], U] = fn
|
||||||
|
|
||||||
|
def __or__(self, next_fn: Callable[[U], V], /) -> FnChain[V]:
|
||||||
|
return FnChain(lambda: next_fn(self._fn()))
|
||||||
|
|
||||||
|
def result(self) -> U:
|
||||||
|
return self._fn()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def transform(cls, x: V, /) -> FnChain[V]:
|
||||||
|
return FnChain(lambda: x)
|
||||||
|
|||||||
53
scripts/00_migrate_links.py
Normal file
53
scripts/00_migrate_links.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import json
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from folkugat_web.dal.sql import get_connection
|
||||||
|
from folkugat_web.dal.sql.temes import links as links_dal
|
||||||
|
from folkugat_web.model.temes import ContentType, Link, LinkType
|
||||||
|
from folkugat_web.utils import map_none
|
||||||
|
|
||||||
|
query = """ SELECT id, links FROM temes """
|
||||||
|
LinkRowTuple = tuple[int, str]
|
||||||
|
|
||||||
|
|
||||||
|
class OldLinkRowDict(TypedDict):
|
||||||
|
content_type: str
|
||||||
|
link_type: str | None
|
||||||
|
title: str
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
def _build_link(tema_id: int, link_dict: OldLinkRowDict) -> Link:
|
||||||
|
return Link(
|
||||||
|
id=None,
|
||||||
|
tema_id=tema_id,
|
||||||
|
content_type=ContentType(link_dict["content_type"]),
|
||||||
|
link_type=map_none(LinkType, link_dict.get("link_type")),
|
||||||
|
url=link_dict["url"],
|
||||||
|
title=link_dict["title"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_links(row: LinkRowTuple) -> list[Link]:
|
||||||
|
tema_id = row[0]
|
||||||
|
link_dicts: list[OldLinkRowDict] = json.loads(row[1])
|
||||||
|
return [_build_link(tema_id, link_dict) for link_dict in link_dicts]
|
||||||
|
|
||||||
|
|
||||||
|
with get_connection() as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query)
|
||||||
|
iter_rows: Iterable[LinkRowTuple] = cur.fetchall()
|
||||||
|
rows = list(map(_build_links, iter_rows))
|
||||||
|
|
||||||
|
_ = cur.execute("DELETE FROM tema_links")
|
||||||
|
for links in rows:
|
||||||
|
for link in links:
|
||||||
|
_ = links_dal.insert_link(link=link, con=con)
|
||||||
|
|
||||||
|
drop_query = """ ALTER TABLE temes DROP COLUMN links"""
|
||||||
|
_ = cur.execute(drop_query)
|
||||||
|
|
||||||
|
|
||||||
|
print("DONE!")
|
||||||
48
scripts/01_migrate_lyrics.py
Normal file
48
scripts/01_migrate_lyrics.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import json
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from folkugat_web.dal.sql import get_connection
|
||||||
|
from folkugat_web.dal.sql.temes import lyrics as lyrics_dal
|
||||||
|
from folkugat_web.model.temes import Lyrics
|
||||||
|
|
||||||
|
query = """ SELECT id, lyrics FROM temes """
|
||||||
|
LyricRowTuple = tuple[int, str]
|
||||||
|
|
||||||
|
|
||||||
|
class OldLyricRowDict(TypedDict):
|
||||||
|
title: str
|
||||||
|
content: str
|
||||||
|
|
||||||
|
|
||||||
|
def _build_lyric(tema_id: int, lyric_dict: OldLyricRowDict) -> Lyrics:
|
||||||
|
return Lyrics(
|
||||||
|
id=None,
|
||||||
|
tema_id=tema_id,
|
||||||
|
title=lyric_dict["title"],
|
||||||
|
content=lyric_dict["content"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_lyrics(row: LyricRowTuple) -> list[Lyrics]:
|
||||||
|
tema_id = row[0]
|
||||||
|
lyric_dicts: list[OldLyricRowDict] = json.loads(row[1])
|
||||||
|
return [_build_lyric(tema_id, lyric_dict) for lyric_dict in lyric_dicts]
|
||||||
|
|
||||||
|
|
||||||
|
with get_connection() as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query)
|
||||||
|
iter_rows: Iterable[LyricRowTuple] = cur.fetchall()
|
||||||
|
rows = list(map(_build_lyrics, iter_rows))
|
||||||
|
|
||||||
|
_ = cur.execute("DELETE FROM tema_lyrics")
|
||||||
|
for lyrics in rows:
|
||||||
|
for lyric in lyrics:
|
||||||
|
_ = lyrics_dal.insert_lyric(lyric=lyric, con=con)
|
||||||
|
|
||||||
|
drop_query = """ ALTER TABLE temes DROP COLUMN lyrics"""
|
||||||
|
_ = cur.execute(drop_query)
|
||||||
|
|
||||||
|
|
||||||
|
print("DONE!")
|
||||||
48
scripts/02_migrate_properties.py
Normal file
48
scripts/02_migrate_properties.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import json
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from folkugat_web.dal.sql import get_connection
|
||||||
|
from folkugat_web.dal.sql.temes import properties as properties_dal
|
||||||
|
from folkugat_web.model.temes import Property, PropertyField
|
||||||
|
|
||||||
|
query = """ SELECT id, properties FROM temes """
|
||||||
|
PropertyRowTuple = tuple[int, str]
|
||||||
|
|
||||||
|
|
||||||
|
class OldPropertyRowDict(TypedDict):
|
||||||
|
field: str
|
||||||
|
value: str
|
||||||
|
|
||||||
|
|
||||||
|
def _build_property(tema_id: int, property_dict: OldPropertyRowDict) -> Property:
|
||||||
|
return Property(
|
||||||
|
id=None,
|
||||||
|
tema_id=tema_id,
|
||||||
|
field=PropertyField(property_dict["field"]),
|
||||||
|
value=property_dict["value"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_properties(row: PropertyRowTuple) -> list[Property]:
|
||||||
|
tema_id = row[0]
|
||||||
|
property_dicts: list[OldPropertyRowDict] = json.loads(row[1])
|
||||||
|
return [_build_property(tema_id, property_dict) for property_dict in property_dicts]
|
||||||
|
|
||||||
|
|
||||||
|
with get_connection() as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
_ = cur.execute(query)
|
||||||
|
iter_rows: Iterable[PropertyRowTuple] = cur.fetchall()
|
||||||
|
rows = list(map(_build_properties, iter_rows))
|
||||||
|
|
||||||
|
_ = cur.execute("DELETE FROM tema_properties")
|
||||||
|
for properties in rows:
|
||||||
|
for property in properties:
|
||||||
|
_ = properties_dal.insert_property(property=property, con=con)
|
||||||
|
|
||||||
|
drop_query = """ ALTER TABLE temes DROP COLUMN properties"""
|
||||||
|
_ = cur.execute(drop_query)
|
||||||
|
|
||||||
|
|
||||||
|
print("DONE!")
|
||||||
Reference in New Issue
Block a user