diff --git a/kontor-api/src/apis/base.py b/kontor-api/src/apis/base.py index c632977..9141715 100644 --- a/kontor-api/src/apis/base.py +++ b/kontor-api/src/apis/base.py @@ -1,9 +1,10 @@ from fastapi import APIRouter -from src.apis.version1 import comic, media, tysc, admin +from src.apis.version1 import comic, mediaactor, mediafile, tysc, admin api_router = APIRouter(prefix="/api") api_router.include_router(comic.router, prefix="/comics", tags=["comics"]) -api_router.include_router(media.router, prefix="/media", tags=["media"]) +api_router.include_router(mediafile.router, prefix="/media", tags=["media"]) +api_router.include_router(mediaactor.router, prefix="/media", tags=["media"]) api_router.include_router(tysc.router, prefix="/tysc", tags=["tysc"]) api_router.include_router(admin.router, prefix="/login", tags=["login"]) diff --git a/kontor-api/src/apis/version1/mediaactor.py b/kontor-api/src/apis/version1/mediaactor.py new file mode 100644 index 0000000..facf69c --- /dev/null +++ b/kontor-api/src/apis/version1/mediaactor.py @@ -0,0 +1,21 @@ +from typing import List, AnyStr + +from fastapi import APIRouter, status, HTTPException, Depends +from sqlalchemy import select, Sequence +from src.core.log_conf import logger +from src.apis.utils import SessionDep +from src.db.repository.media import create_new_mediafile +from src.schema.media.actor import MediaActorResponse +from src.db.models.media import MediaActor + +router = APIRouter() + +@router.get("/actors", response_model=List[MediaActorResponse]) +#def get_all_files(db: SessionDep, review: bool = False, download: bool = False, current_user: Profile = Depends(get_current_user_from_token)) -> List[MediaFileResponse]: +def get_all_files(db: SessionDep, review: bool = False, download: bool = False) -> List[MediaActorResponse]: + results: List[MediaActorResponse] = [] + actors = db.scalars(select(MediaActor)).all() + for mediaactor in actors: + response = MediaActorResponse(id=mediaactor.id, name=str(mediaactor.name), url=str(mediaactor.url)) + results.append(response) + return results diff --git a/kontor-api/src/apis/version1/media.py b/kontor-api/src/apis/version1/mediafile.py similarity index 54% rename from kontor-api/src/apis/version1/media.py rename to kontor-api/src/apis/version1/mediafile.py index 1014b1e..3a672bc 100644 --- a/kontor-api/src/apis/version1/media.py +++ b/kontor-api/src/apis/version1/mediafile.py @@ -4,7 +4,9 @@ from fastapi import APIRouter, status, HTTPException, Depends from sqlalchemy import select, Sequence from src.core.log_conf import logger from src.apis.utils import SessionDep -from src.db.repository.media import create_new_mediafile +from src.db.repository.media import create_new_mediaactorfile, create_new_mediafile +from src.schema.media.actor import MediaActorResponse +from src.schema.media.actorfile import MediaActorFileResponse from src.schema.media.file import MediaFileResponse, Link, get_file_details, set_file from src.db.models.media import MediaFile @@ -47,6 +49,43 @@ def get_file(file_id: AnyStr, db: SessionDep) -> MediaFileResponse: response = get_file_details(mediafile) return response +@router.get("/files/{file_id}/actors", response_model=List[MediaActorResponse]) +def get_file_actors(file_id: AnyStr, db: SessionDep) -> List[MediaActorResponse]: + mediafile = db.get(MediaFile, file_id) + if not mediafile: + raise HTTPException(status_code=404, detail="MediaFile could not be found") + actor_files = mediafile.media_actor_files + logger.info(f"already known actors: {actor_files}") + results: List[MediaActorResponse] = [] + for actor_file in actor_files: + response = MediaActorResponse(id=actor_file.media_actor.id, name=actor_file.media_actor.name, url=actor_file.media_actor.url) + results.append(response) + return results + +@router.put("/files/{file_id}/actors", response_model=List[MediaActorFileResponse]) +def update_file_actors(file_id: AnyStr, db: SessionDep, actors: List[MediaActorResponse]) -> List[MediaActorFileResponse]: + mediafile = db.get(MediaFile, file_id) + if not mediafile: + raise HTTPException(status_code=404, detail="MediaFile could not be found") + actor_files = mediafile.media_actor_files + logger.info(f"already known actors: {actor_files}") + for actor in actors: + already_associated = False + for actor_file in actor_files: + if actor.id == actor_file.media_actor_id: + logger.info("alreay associated - do nothing") + already_associated = True + break + if not already_associated: + create_new_mediaactorfile(db, actor.id, mediafile.id) + db.refresh(mediafile) + actor_files = mediafile.media_actor_files + results: List[MediaActorFileResponse] = [] + for actor_file in actor_files: + response = MediaActorFileResponse(id=actor_file.id, actor_id=actor_file.media_actor_id, file_id=actor_file.media_file_id) + results.append(response) + return results + @router.put("/files/{file_id}", response_model=MediaFileResponse) def update_file(file_id: AnyStr, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse: mediaFile = db.get(MediaFile, file_id) @@ -55,7 +94,11 @@ def update_file(file_id: AnyStr, db: SessionDep, info: MediaFileResponse) -> Med set_file(info, mediaFile) db.add(mediaFile) db.commit() - return info + mediafile = db.get(MediaFile, file_id) + if not mediafile: + raise HTTPException(status_code=404, detail="MediaFile could not be updated") + response = get_file_details(mediafile) + return response @router.post("/files", status_code=status.HTTP_201_CREATED) diff --git a/kontor-api/src/db/models/base.py b/kontor-api/src/db/models/base.py index 74b3ece..79755ff 100644 --- a/kontor-api/src/db/models/base.py +++ b/kontor-api/src/db/models/base.py @@ -21,10 +21,10 @@ class BaseMixin: class BaseVideoMixin: - cloud_link = Column(String) - file_name = Column(String) + cloud_link = Column(String, nullable=True) + file_name = Column(String, nullable=True) path = Column(String) review = Column(Boolean) title = Column(String) - url = Column(String, unique=True) + url = Column(String, nullable=True) should_download = Column(Boolean) diff --git a/kontor-api/src/db/models/media.py b/kontor-api/src/db/models/media.py index ae360b3..fe33264 100644 --- a/kontor-api/src/db/models/media.py +++ b/kontor-api/src/db/models/media.py @@ -71,7 +71,7 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin): class MediaActor(Base, BaseMixin): __tablename__ = 'media_actor' name = Column(String) - url = Column(String, unique=True) + url = Column(String, unique=True, nullable=True) media_actor_files = relationship("MediaActorFile") @@ -82,6 +82,11 @@ class MediaActorFile(Base, BaseMixin): media_file_id = Column(String, ForeignKey("media_file.id"), nullable=True) media_file = relationship("MediaFile", back_populates="media_actor_files") + def __repr__(self): + return f'MediaActorFile({self.id} {self.media_actor_id} {self.media_file_id})' + + def __str__(self) -> str: + return f'{self.id} {self.media_actor_id} {self.media_file_id}' class MediaArticle(Base, BaseMixin): __tablename__ = 'media_article' diff --git a/kontor-api/src/db/repository/media.py b/kontor-api/src/db/repository/media.py index ed665ae..61cc167 100644 --- a/kontor-api/src/db/repository/media.py +++ b/kontor-api/src/db/repository/media.py @@ -3,7 +3,7 @@ from typing import AnyStr import uuid from datetime import datetime from src.core.log_conf import logger -from src.db.models.media import MediaFile, MediaVideo +from src.db.models.media import MediaActorFile, MediaFile, MediaVideo from src.webapps.media.forms import AddLinkForm @@ -38,3 +38,16 @@ def create_new_mediafile(link: AnyStr, db: Session) -> MediaFile: logger.info(f"created {media_file}") return media_file +def create_new_mediaactorfile(db: Session, actor_id: AnyStr, file_id: AnyStr) -> MediaActorFile: + logger.info(f"create MediaActorFile with actor {actor_id} and file {file_id}") + media_actor_file: MediaActorFile = MediaActorFile() + media_actor_file.id = str(uuid.uuid4()) + media_actor_file.created_date = datetime.now() + media_actor_file.last_modified_date = datetime.now() + media_actor_file.version = 0 + media_actor_file.media_actor_id = actor_id + media_actor_file.media_file_id = file_id + db.add(media_actor_file) + db.commit() + db.refresh(media_actor_file) + return media_actor_file diff --git a/kontor-api/src/schema/media/actor.py b/kontor-api/src/schema/media/actor.py new file mode 100644 index 0000000..c800964 --- /dev/null +++ b/kontor-api/src/schema/media/actor.py @@ -0,0 +1,10 @@ +from datetime import datetime + +from src.db.models.media import MediaActor +from pydantic import BaseModel + + +class MediaActorResponse(BaseModel): + id: str + name: str + url: str diff --git a/kontor-api/src/schema/media/actorfile.py b/kontor-api/src/schema/media/actorfile.py new file mode 100644 index 0000000..6bdfd2d --- /dev/null +++ b/kontor-api/src/schema/media/actorfile.py @@ -0,0 +1,10 @@ +from datetime import datetime + +from src.db.models.media import MediaFile +from pydantic import BaseModel + + +class MediaActorFileResponse(BaseModel): + id: str + file_id: str + actor_id: str diff --git a/kontor-api/src/schema/media/file.py b/kontor-api/src/schema/media/file.py index ba4afb5..3cccccc 100644 --- a/kontor-api/src/schema/media/file.py +++ b/kontor-api/src/schema/media/file.py @@ -9,14 +9,14 @@ class MediaFileResponse(BaseModel): title: str | None = None file_name: str | None = None cloud_link: str | None = None - url: str + url: str | None = None review: bool = False should_download: bool = False class Link(BaseModel): url: str -def get_file_details(mediafile: MediaFile) -> MediaFileResponse | None: +def get_file_details(mediafile: MediaFile) -> MediaFileResponse: response = MediaFileResponse(id=mediafile.id, title=mediafile.title, file_name=mediafile.file_name, diff --git a/kontor-api/src/static/style.css b/kontor-api/src/static/style.css new file mode 100644 index 0000000..8b5c0c3 --- /dev/null +++ b/kontor-api/src/static/style.css @@ -0,0 +1,172 @@ +* { + box-sizing: border-box; +} + +body { + font-family: Arial; + padding: 10px; + background: lightgrey; +} + +/* Header/Blog Title */ +.header { + padding: 30px; + text-align: center; + background-color: lightblue; +} + +.header h1 { + font-size: 50px; +} + +/* Style the top navigation bar */ +.topnav { + overflow: hidden; + background-color: darkgrey; +} + +/* Style the topnav links */ +.topnav a { + float: left; + display: block; + color: #f2f2f2; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} + +/* Change color on hover */ +.topnav a:hover { + background-color: #ddd; + color: black; +} + +.subnav { + overflow: hidden; + background-color: grey; +} + +.subnav a { + float: left; + display: block; + color: #f2f2f2; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} + +.form-inline { + display: flex; + flex-flow: row wrap; + align-items: center; + padding-top: 10px; + padding-left: 20px; +} + +.form-inline label { + margin: 5px 10px 5px 0; +} + +.form-inline input { + padding-right: 50px; + margin-right: 10px; +} + +.form-inline button { + padding: 10px 20px; + background-color: dodgerblue; + border: 1px solid #ddd; + color: white; + border-radius: 10px; +} + +.form-inline button:hover { + background-color: royalblue; +} + +.pill-nav a { + display: inline-block; + color: white; + background-color: dodgerblue; + text-align: center; + padding: 10px; + text-decoration: none; + border-radius: 10px; + margin-left: 20px; +} + +/* Change the color of links on mouse-over */ +.pill-nav a:hover { + background-color: royalblue; + color: white; +} + +/* Add a color to the active/current link */ +.pill-nav a.active { + background-color: royalblue; + color: white; +} + +.maincolumn { + float:left; + width: 100%; +} +/* Create two unequal columns that floats next to each other */ +/* Left column */ +.leftcolumn { + float: left; + width: 75%; +} + +/* Right column */ +.rightcolumn { + float: left; + width: 25%; + background-color: #f1f1f1; + padding-left: 20px; +} + +/* Fake image */ +.fakeimg { + background-color: #aaa; + width: 100%; + padding: 20px; +} + +/* Add a card effect for articles */ +.card { + background-color: white; + padding: 20px; + margin-top: 20px; +} + +/* Clear floats after the columns */ +.row::after { + content: ""; + display: table; + clear: both; +} + +/* Footer */ +.footer { + padding: 20px; + text-align: center; + background: #ddd; + margin-top: 20px; +} + +/* Responsive layout - when the screen is less than 800px wide, make the two columns stack on top of each other instead of next to each other */ +@media screen and (max-width: 800px) { + .leftcolumn, .rightcolumn { + width: 100%; + padding: 0; + } +} + +/* Responsive layout - when the screen is less than 400px wide, make the navigation links stack on top of each other instead of next to each other */ +@media screen and (max-width: 400px) { + .topnav a { + float: none; + width: 100%; + } +} diff --git a/kontor-api/src/templates/comic/comics.html b/kontor-api/src/templates/comic/comics.html index cd7a1f9..0a4ce63 100644 --- a/kontor-api/src/templates/comic/comics.html +++ b/kontor-api/src/templates/comic/comics.html @@ -4,23 +4,39 @@ Comic List {% endblock %} +{% block header %} +

Comics..

+{% endblock %} + +{% block subnav %} + +{% endblock %} + {% block content %} {% with msg=msg %} {% include "components/alerts.html" %} {% endwith %} -
-
-
- - Completed - Order - -
-
-
-

Comics..

-
-
+
+
+ + + + +
+ Add Comic +
+
+
+
+
@@ -36,13 +52,10 @@ - + {% endfor %}
Title{% with check=comic.completed %}{% include "components/check.html" %}{% endwith %} Edit Delete
-
- Add Comic -
{% endblock %} diff --git a/kontor-api/src/templates/components/footer.html b/kontor-api/src/templates/components/footer.html new file mode 100644 index 0000000..e1ea0b7 --- /dev/null +++ b/kontor-api/src/templates/components/footer.html @@ -0,0 +1 @@ +

Footer

\ No newline at end of file diff --git a/kontor-api/src/templates/components/navbar.html b/kontor-api/src/templates/components/navbar.html index 02cc139..7a25805 100644 --- a/kontor-api/src/templates/components/navbar.html +++ b/kontor-api/src/templates/components/navbar.html @@ -1,4 +1,12 @@ -