Vorbereitung Release 0.2.0

This commit is contained in:
2026-01-29 23:50:41 +01:00
parent 58f80b3e76
commit b26b5ecc9c
571 changed files with 35728 additions and 5022 deletions
@@ -0,0 +1,29 @@
from typing import Optional
from fastapi import APIRouter, Request
from fastapi.templating import Jinja2Templates
from src.db.models.admin import Permission, Profile
from src.db.session import SessionDep
templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False, prefix="/admin")
@router.get("/profiles")
def get_profiles(db: SessionDep, request: Request, msg: Optional[str] = None):
profiles = db.query(Profile).all()
return templates.TemplateResponse("admin/profiles.html", {"request": request, "msg": msg, "profiles": profiles})
@router.get("/profiles/{profile_id}")
def comic_details(profile_id: str, request: Request, db: SessionDep):
profile = db.get(Profile, profile_id)
return templates.TemplateResponse("admin/profile_detail.html", {"request": request, "profile":profile})
@router.get("/permissions")
def get_permissions(db: SessionDep, request: Request, msg: Optional[str] = None):
permissions = db.query(Permission).all()
return templates.TemplateResponse("admin/permissions.html", {"request": request, "msg": msg, "permissions": permissions})
@router.get("/permissions/{permission_id}")
def artist_detail(permission_id: str, request: Request, db: SessionDep):
permission= db.get(Permission, str(permission_id))
return templates.TemplateResponse("comic/permission_detail.html", {"request": request, "permission": permission})
+27
View File
@@ -0,0 +1,27 @@
from typing import List
from typing import Optional
from fastapi import Request
class LoginForm:
def __init__(self, request: Request):
self.request: Request = request
self.errors: List = []
self.username: Optional[str] = None
self.password: Optional[str] = None
async def load_data(self):
form = await self.request.form()
# since auth works on username field we are considering email as username
self.username = form.get("email")
self.password = form.get("password")
async def is_valid(self):
if not self.username or not (self.username.__contains__("@")):
self.errors.append("Email is required")
if not self.password or not len(self.password) >= 4:
self.errors.append("A valid password is required")
if not self.errors:
return True
return False
@@ -0,0 +1,36 @@
# from src.apis.version1.admin import login_for_access_token
from fastapi.security import OAuth2PasswordRequestForm
from src.apis.version1.admin import login_for_token_cookie
from src.db.session import SessionDep
from fastapi import APIRouter, Depends
from fastapi import HTTPException
from fastapi import Request
from fastapi.templating import Jinja2Templates
from src.webapps.auth.forms import LoginForm
templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False)
@router.get("/login/")
def login(request: Request):
return templates.TemplateResponse("auth/login.html", {"request": request})
@router.post("/login/")
async def login(request: Request):
form = LoginForm(request)
await form.load_data()
if await form.is_valid():
try:
form.__dict__.update(msg="Login Successful :)")
response = templates.TemplateResponse("auth/login.html", form.__dict__)
login_for_token_cookie(response=response, form_data=form)
return response
except HTTPException:
form.__dict__.update(msg="")
form.__dict__.get("errors").append("Incorrect Email or Password")
return templates.TemplateResponse("auth/login.html", form.__dict__)
return templates.TemplateResponse("auth/login.html", form.__dict__)
+11 -3
View File
@@ -1,15 +1,23 @@
from fastapi import APIRouter, Request
from fastapi.templating import Jinja2Templates
from src.webapps.comic import route_comics
from src.webapps.media import route_media
from src.webapps.admin import route_admin
from src.webapps.auth import route_login
from src.webapps.comic import route_comics, route_worktype, route_artists
from src.webapps.media import route_actors, route_media, route_videos
templates = Jinja2Templates(directory="src/templates")
api_router = APIRouter()
api_router.include_router(route_comics.router)
api_router.include_router(route_artists.router)
api_router.include_router(route_worktype.router)
api_router.include_router(route_media.router)
api_router.include_router(route_actors.router)
api_router.include_router(route_videos.router)
api_router.include_router(route_login.router)
api_router.include_router(route_admin.router)
@api_router.get("/")
def home(request: Request, msg: str = None):
def home(request: Request, msg: str | None = None):
return templates.TemplateResponse("index.html", {"request": request, "msg": msg})
@@ -0,0 +1,26 @@
from fastapi import Request
from typing import List, Optional
class AddArtistForm:
def __init__(self, request: Request, artist_id: str, artist_name: str, artist_link: str):
self.request = request
self.errors: List = []
self.id: str = artist_id
self.name: Optional[str] = artist_name
self.link: Optional[str] = artist_link
async def load_data(self):
form = await self.request.form()
print(f"{form.keys()}")
self.name = form.get("artist.name")
self.link = form.get("artist.link")
def is_valid(self):
if not self.errors:
return True
return False
def __str__(self):
return f"{self.name=}, {self.link=}"
@@ -0,0 +1,27 @@
from fastapi import Request
from typing import List, Optional
class ValidateComicForm:
def __init__(self, request: Request, comic_id: str, completed: bool, current_order: bool):
self.request = request
self.errors: List = []
self.id = comic_id
self.title: Optional[str] = None
self.weblink: Optional[str] = None
self.completed = completed
self.current_order = current_order
async def load_data(self):
form = await self.request.form()
print(f"{form.keys()}")
self.title = form.get("title")
self.weblink = form.get("weblink")
def is_valid(self):
if not self.errors:
return True
return False
def __str__(self):
return f"{self.title=}, {self.weblink=}"
@@ -0,0 +1,20 @@
from fastapi import Request
from typing import List, Optional
class AddWorktypeForm:
def __init__(self, request: Request):
self.request = request
self.errors: List = []
self.worktype: Optional[str] = None
async def load_data(self):
form = await self.request.form()
self.worktype = form.get("worktype")
def is_valid(self):
if not self.worktype or (len(self.worktype) == 0):
self.errors.append("WorkType cannot be empty")
if not self.errors:
return True
return False
@@ -0,0 +1,59 @@
from fastapi import APIRouter, Request, status, Form
from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse
from src.db.models.comic import Artist
from typing import AnyStr
from src.db.repository.comics.artist import update_artist
from src.db.session import SessionDep
#from src.db.repository.comic import create_new_worktype, update_worktype
from src.main import logger
from src.schema.comics.artist import AddArtist
from src.webapps.comic.forms.artist import AddArtistForm
#from src.schema.comics.worktype import AddWorkType
#from src.webapps.comic.forms import AddWorktypeForm
templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False, prefix="/comic")
@router.get("/artists")
def get_artists(db: SessionDep, request: Request, msg: str | None = None):
artists = db.query(Artist).all()
return templates.TemplateResponse("comic/artists.html", {"request": request, "msg": msg, "artists": artists})
@router.get("/artists/{artist_id}")
def artist_detail(artist_id: AnyStr, request: Request, db: SessionDep):
artist = db.get(Artist, str(artist_id))
return templates.TemplateResponse("comic/artist_detail.html", {"request": request, "artist": artist})
@router.get("/artist/edit/{artist_id}")
def edit_artist(db: SessionDep, request: Request, artist_id: str):
artist = db.get(Artist, artist_id)
return templates.TemplateResponse("comic/artist_edit.html", {"request": request, "artist_name": artist.name, "artist_link": artist.weblink})
@router.post("/artist/edit/{artist_id}")
async def edit_artist(request: Request, db: SessionDep, artist_id: str, action: str = Form(...), artist_name: str = Form(...), artist_link: str = Form(...)):
if action == "cancel":
return RedirectResponse(f"/comic/artists/{artist_id}", status_code=status.HTTP_303_SEE_OTHER)
form = AddArtistForm(request, artist_id, artist_name, artist_link)
await form.load_data()
if form.is_valid():
try:
artist = AddArtist(**form.__dict__)
artist = update_artist(add_artist=artist, artist_id=artist_id, db=db)
return RedirectResponse(f"/comic/artists/{artist.id}", status_code=status.HTTP_303_SEE_OTHER)
except Exception as e:
print(e)
form.__dict__.get("errors").append("artist already added")
return templates.TemplateResponse("comic/artist_edit.html", form.__dict__)
return templates.TemplateResponse("comic/artist_edit.html", form.__dict__)
@router.get("/artist/delete/{artist_id}")
async def delete_artist(db: SessionDep, request: Request, artist_id: str):
artist = db.get(Artist, artist_id)
db.delete(artist)
db.commit()
return RedirectResponse("/comic/artists", status_code=status.HTTP_303_SEE_OTHER)
+66 -18
View File
@@ -1,42 +1,90 @@
from uuid import UUID
from fastapi import APIRouter, Request
from fastapi import APIRouter, Form, Request, status
from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse
from src.apis.utils import SessionDep
from src.db.models.comic import Comic, Artist, Publisher
from src.db.models.comic import Comic, Publisher, Issue
from typing import AnyStr
from src.core.log_conf import logger
from src.db.repository.comics.comic import update_comic
from src.db.session import SessionDep
from src.schema.comics.comic import ComicSchema
from src.webapps.comic.forms.comic import ValidateComicForm
templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False, prefix="/comic")
@router.get("/comics")
def get_comics(db: SessionDep, request: Request, msg: str = None):
comics = db.query(Comic).all()
def get_comics(db: SessionDep, request: Request, msg: str | None = None):
params = request.query_params
query = params.get("query")
filter = {}
completed = params.get('completed') == "on"
if completed:
filter['completed'] = True
order = params.get("order") == "on"
if order:
filter['current_order'] = True
if query is not None and len(query) > 0:
filter['title'] = query
if len(filter) > 0:
if "title" in filter:
comics = db.query(Comic).filter(Comic.title.ilike(f'%{query}%'))
else:
comics = db.query(Comic).filter_by(**filter).all()
else:
comics = db.query(Comic).all()
return templates.TemplateResponse("comic/comics.html", {"request": request, "msg": msg, "comics": comics})
@router.get("/comics/{comic_id}")
def comic_details(comic_id: UUID, request: Request, db: SessionDep):
def comic_details(comic_id: AnyStr, request: Request, db: SessionDep):
comic = db.get(Comic, comic_id)
return templates.TemplateResponse("comic/comic_detail.html", {"request": request, "comic":comic})
@router.get("/artists")
def get_artists(db: SessionDep, request: Request, msg: str = None):
artists = db.query(Artist).all()
return templates.TemplateResponse("comic/artists.html", {"request": request, "msg": msg, "artists": artists})
@router.get("/comic/edit/{comic_id}")
def edit_comic(db: SessionDep, request: Request, comic_id: str):
comic = db.get(Comic, comic_id)
return templates.TemplateResponse("comic/comic_edit.html", {"request": request, "comic_title": comic.title, "comic_weblink": comic.weblink})
@router.get("/artists/{artist_id}")
def artist_detail(artist_id: UUID, request: Request, db: SessionDep):
artist = db.get(Artist, artist_id)
return templates.TemplateResponse("comic/artist_detail.html", {"request": request, "artist": artist})
@router.post("/comic/edit/{comic_id}")
async def validate_comic(request: Request, db: SessionDep, comic_id: str, action: str = Form(...), completed: bool = Form(False), current_order: bool = Form(False)):
if action == "cancel":
return RedirectResponse(f"/comic/comics/{comic_id}", status_code=status.HTTP_303_SEE_OTHER)
form = ValidateComicForm(request, comic_id, completed, current_order)
logger.info(f"request: {repr(request)}")
await form.load_data()
logger.info(f"form: {form}")
if form.is_valid():
try:
comic = ComicSchema(**form.__dict__)
comic = update_comic(comic=comic, comic_id=comic_id, db=db)
return RedirectResponse(f"/comic/comics/{comic.id}", status_code=status.HTTP_303_SEE_OTHER)
except Exception as e:
print(e)
form.__dict__.get("errors").append("comic already added")
return templates.TemplateResponse("comic/comic_edit.html", form.__dict__)
return templates.TemplateResponse("comic/comic_edit.html", form.__dict__)
@router.get("/publishers")
def get_publishers(db: SessionDep, request: Request, msg: str = None):
def get_publishers(db: SessionDep, request: Request, msg: str | None = None):
publishers = db.query(Publisher).all()
return templates.TemplateResponse("comic/publishers.html", {"request": request, "publishers": publishers})
@router.get("/publishers/{publisher_id}")
def publisher_details(publisher_id: UUID, request: Request, db: SessionDep, msg: str = None):
def publisher_details(publisher_id: AnyStr, request: Request, db: SessionDep, msg: str = None):
publisher = db.get(Publisher, publisher_id)
if publisher is None:
msg = "Could not find Publisher"
return templates.TemplateResponse("comic/publisher_detail.html", {"request": request, "msg": msg, "publisher": publisher})
@router.get("/issues")
def get_issues(db: SessionDep, request: Request, msg: str | None = None):
issues = db.query(Issue).all()
return templates.TemplateResponse("comic/issues.html", {"request": request, "msg": msg, "issues": issues})
@router.get("/issues/{issue_id}")
def issue_details(issue_id: AnyStr, request: Request, db: SessionDep):
issue = db.get(Issue, issue_id)
return templates.TemplateResponse("comic/issue_detail.html", {"request": request, "issue": issue})
@@ -0,0 +1,73 @@
from fastapi import APIRouter, Request, status
from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse
from src.db.models.comic import WorkType
from typing import AnyStr
from src.db.repository.comics.worktype import create_new_worktype, update_worktype
from src.db.session import SessionDep
from src.main import logger
from src.schema.comics.worktype import AddWorkType
from src.webapps.comic.forms.worktype import AddWorktypeForm
templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False, prefix="/comic")
@router.get("/worktypes")
def get_worktypes(db: SessionDep, request: Request, msg: str | None = None):
worktypes = db.query(WorkType).all()
return templates.TemplateResponse("comic/worktypes.html", {"request": request, "msg": msg, "worktypes": worktypes})
@router.get("/worktypes/{worktype_id}")
def worktype_detail(db: SessionDep, request: Request, worktype_id: AnyStr):
worktype = db.get(WorkType, worktype_id)
return templates.TemplateResponse("comic/worktype_detail.html", {"request": request, "worktype": worktype})
@router.get("/worktype/add")
def add_worktype(request: Request, db: SessionDep):
return templates.TemplateResponse("comic/worktype_edit.html", {"request": request})
@router.post("/worktype/add")
async def add_worktype_post(db: SessionDep, request: Request):
form = AddWorktypeForm(request)
await form.load_data()
if form.is_valid():
try:
work = AddWorkType(**form.__dict__)
worktype = create_new_worktype(work=work, db=db)
logger.info(f"add_worktype: redirect to /comic/worktypes/{worktype.id}")
return RedirectResponse(f"/comic/worktypes/{worktype.id}", status_code=status.HTTP_303_SEE_OTHER)
except Exception as e:
print(e)
form.__dict__.get("errors").append("worktype already added")
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
print("form is not valid")
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
@router.get("/worktype/edit/{worktype_id}")
def edit_worktype(db: SessionDep, request: Request, worktype_id: str):
worktype: WorkType | None = db.get(WorkType, worktype_id)
return templates.TemplateResponse("comic/worktype_edit.html", {"request": request, "worktype": worktype.name})
@router.post("/worktype/edit/{worktype_id}")
async def edit_worktype_post(request: Request, db: SessionDep, worktype_id: str):
form = AddWorktypeForm(request)
await form.load_data()
if form.is_valid():
try:
work = AddWorkType(**form.__dict__)
worktype = update_worktype(work=work, worktype_id=worktype_id, db=db)
return RedirectResponse(f"/comic/worktypes/{worktype.id}", status_code=status.HTTP_303_SEE_OTHER)
except Exception as e:
print(e)
form.__dict__.get("errors").append("worktype already added")
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
@router.get("/worktype/delete/{worktype_id}")
async def delete_worktype(db: SessionDep, request: Request, worktype_id: str):
worktype = db.get(WorkType, worktype_id)
db.delete(worktype)
db.commit()
return RedirectResponse("/comic/worktypes", status_code=status.HTTP_303_SEE_OTHER)
+20
View File
@@ -0,0 +1,20 @@
from fastapi import Request
from typing import List, Optional
class AddLinkForm:
def __init__(self, request: Request):
self.request = request
self.errors: List = []
self.url: Optional[str] = None
async def load_data(self):
form = await self.request.form()
self.url = form.get("url")
def is_valid(self):
if not self.url or not (self.url.__contains__("http")):
self.errors.append("Valid Url is required e.g. https://example.com")
if not self.errors:
return True
return False
@@ -0,0 +1,21 @@
from typing import AnyStr
from fastapi import APIRouter, Request
from fastapi.templating import Jinja2Templates
from src.db.models.media import MediaActor
from src.db.session import SessionDep
templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False, prefix="/media")
@router.get("/actors")
def get_actors(db: SessionDep, request: Request, msg: str | None = None):
actors = db.query(MediaActor).all()
return templates.TemplateResponse("media/actors.html", {"request": request, "msg": msg, "actors": actors})
@router.get("/actors/{actor_id}")
def artist_detail(actor_id: AnyStr, request: Request, db: SessionDep):
actor = db.get(MediaActor, actor_id)
return templates.TemplateResponse("media/actor_detail.html", {"request": request, "actor": actor})
+42 -18
View File
@@ -1,32 +1,56 @@
from uuid import UUID
from typing import AnyStr
from fastapi import APIRouter, Request
from fastapi.security.utils import get_authorization_scheme_param
from fastapi.templating import Jinja2Templates
from sqlalchemy import or_
from src.apis.utils import SessionDep
from src.db.models.media import MediaFile, MediaActor
#ifrom src.schema.media.comic import get_comic_details
from src.core.security import get_current_user_from_token
from src.db.models.admin import Profile
from src.db.models.media import MediaFile
from src.core.log_conf import logger
from src.db.session import SessionDep
templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False, prefix="/media")
@router.get("/files")
def get_mediafiles(db: SessionDep, request: Request, msg: str = None):
mediafiles = db.query(MediaFile).all()
return templates.TemplateResponse("media/files.html", {"request": request, "msg": msg, "mediafiles": mediafiles})
def get_mediafiles(db: SessionDep, request: Request, msg: str | None = None):
params = request.query_params
query = params.get("query")
filter = {}
review = params.get('review') == "on"
if review:
filter['review'] = True
download = params.get("download") == "on"
if download:
filter['should_download'] = True
if query is not None and len(query) > 0:
filter['url'] = query
if len(filter) > 0:
if "url" in filter:
mediafiles = db.query(MediaFile).filter(or_(MediaFile.title.ilike(f'%{query}%'), MediaFile.url.ilike(f"%{query}%")))
else:
mediafiles = db.query(MediaFile).filter_by(**filter).all()
else:
mediafiles = db.query(MediaFile).all()
try:
token = request.cookies.get("access_token")
scheme, param = get_authorization_scheme_param(token) # scheme will hold "Bearer" and param will hold actual token value
current_user: Profile = get_current_user_from_token(token=param)
return templates.TemplateResponse("media/files.html", {"request": request, "msg": msg, "mediafiles": mediafiles})
except Exception as e:
print(e)
logger.info("User is not authorized, no data shown")
msg = "Nicht berechtigt!!"
return templates.TemplateResponse("media/files.html", {"request": request, "msg": msg, "mediafiles": []})
@router.get("/files/{file_id}")
def file_details(file_id: UUID, request: Request, db: SessionDep):
def file_details(file_id: AnyStr, request: Request, db: SessionDep):
mediafile = db.get(MediaFile, file_id)
return templates.TemplateResponse("media/file_detail.html", {"request": request, "mediafile":mediafile})
@router.get("/actors")
def get_actors(db: SessionDep, request: Request, msg: str = None):
actors = db.query(MediaActor).all()
return templates.TemplateResponse("media/actors.html", {"request": request, "msg": msg, "actors": actors})
@router.get("/actors/{actor_id}")
def artist_detail(actor_id: UUID, request: Request, db: SessionDep):
actor = db.get(MediaActor, actor_id)
return templates.TemplateResponse("media/actor_detail.html", {"request": request, "actor": actor})
@router.get("files/edit/{file_id}")
def edit_file(db: SessionDep, request: Request, file_id: str):
media_file = db.get(MediaFile, file_id)
return templates.TemplateResponse("media/file_detail.html", {"request": request, "mediafile":media_file})
@@ -0,0 +1,51 @@
from fastapi import APIRouter, Request, status, responses
from fastapi.security.utils import get_authorization_scheme_param
from fastapi.templating import Jinja2Templates
from src.core.security import get_current_user_from_token
from src.db.models.media import MediaVideo
from src.db.repository.media import create_new_video
from src.db.models.admin import Profile
from src.db.session import SessionDep
from src.schema.media.video import AddLink
from src.webapps.media.forms import AddLinkForm
templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False, prefix="/media")
@router.get("/videos")
def get_mediavideos(db: SessionDep, request: Request, msg: str = None):
mediavideos = db.query(MediaVideo).all()
try:
token = request.cookies.get("access_token")
_, param = get_authorization_scheme_param(token) # scheme will hold "Bearer" and param will hold actual token value
current_user: Profile = get_current_user_from_token(token=param, db=db)
return templates.TemplateResponse("media/videos.html", {"request": request, "msg": msg, "user": current_user, "mediavideos": mediavideos})
except Exception as e:
print(e)
return templates.TemplateResponse("media/videos.html", {"request": request, "msg": msg, "user": None, "mediavideos": mediavideos})
@router.get("/videos/{video_id}")
def video_details(video_id: str, request: Request, db: SessionDep):
mediavideo = db.get(MediaVideo, video_id)
return templates.TemplateResponse("media/video_detail.html", {"request": request, "mediavideo":mediavideo})
@router.get("/add-link")
def add_video_link(request: Request, db: SessionDep):
return templates.TemplateResponse("media/add_video_link.html", {"request": request})
@router.post("/add-link")
async def post_video_link(request: Request, db: SessionDep):
form = AddLinkForm(request)
await form.load_data()
if form.is_valid():
try:
video = AddLink(**form.__dict__)
mediavideo = create_new_video(video=video, db=db)
return responses.RedirectResponse(f"media/videos/{mediavideo.id}", status_code=status.HTTP_302_FOUND)
except Exception as e:
print(e)
form.__dict__.get("errors").append("Link already added")
return templates.TemplateResponse("media/add_video_link.html", form.__dict__)
return templates.TemplateResponse("media/add_video_link.html", form.__dict__)