add login functions for cookie and non-cookie authentication

This commit is contained in:
Thomas Peetz
2025-11-05 21:48:25 +01:00
parent 09c2a350e4
commit f3e47126b3
24 changed files with 279 additions and 142 deletions
-48
View File
@@ -1,48 +0,0 @@
from typing import Annotated
from typing import Dict
from typing import Optional
from fastapi import HTTPException
from fastapi import Request
from fastapi import status
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
from fastapi.security import OAuth2
from fastapi.security.utils import get_authorization_scheme_param
from fastapi import Depends
from sqlalchemy.orm import Session
from src.db.session import get_db
SessionDep = Annotated[Session, Depends(get_db)]
class OAuth2PasswordBearerWithCookie(OAuth2):
def __init__(
self,
tokenUrl: str,
scheme_name: Optional[str] = None,
scopes: Optional[Dict[str, str]] = None,
auto_error: bool = True,
):
if not scopes:
scopes = {}
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes})
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.cookies.get(
"access_token"
) # changed to accept access token from httpOnly Cookie
scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "bearer":
if self.auto_error:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
else:
return None
return param
+26 -42
View File
@@ -1,35 +1,38 @@
import logging
from datetime import timedelta from datetime import timedelta
from typing import Annotated
import bcrypt from fastapi import APIRouter, HTTPException, status, Depends, Response
from fastapi import APIRouter, HTTPException, status, Response, Depends
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from jose import jwt, JWTError
from src.apis.utils import SessionDep, OAuth2PasswordBearerWithCookie
from src.core.config import settings from src.core.config import settings
from src.core.security import create_access_token from src.core.security import create_access_token, authenticate_user, get_current_active_user
from src.db.models.admin import Profile from src.db.models.admin import Profile
from src.db.repository.admin import get_profile from src.db.session import SessionDep
from src.schema.admin import Token from src.schema.admin import Token, ProfileModel
from src.webapps.auth.forms import LoginForm
router = APIRouter() router = APIRouter()
def authenticate_user(username: str, password: str, db: SessionDep) -> Profile | None: @router.post("/token")
user = get_profile(username=username, db=db) def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> Token:
print(user) user = authenticate_user(form_data.username, form_data.password)
if not user: if not user:
return None raise HTTPException(
if bcrypt.checkpw(password.encode(), user.password.encode()): status_code=status.HTTP_401_UNAUTHORIZED,
print("User successful authenticated") detail="Incorrect username or password",
else: headers={"WWW-Authenticate": "Bearer"},
logging.info("Authentication failed!") )
return user access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.email, "scope": " ".join(form_data.scopes)}, expires_delta=access_token_expires
)
return Token(access_token=access_token, token_type="bearer")
@router.post("/token", response_model=Token) # @router.post("/token-cookie", response_model=Token)
def login_for_access_token(response: Response, db: SessionDep, form_data: OAuth2PasswordRequestForm = Depends()): def login_for_token_cookie(response: Response, form_data: LoginForm = Depends()):
user = authenticate_user(form_data.username, form_data.password, db) user = authenticate_user(form_data.username, form_data.password)
if not user: if not user:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
@@ -45,25 +48,6 @@ def login_for_access_token(response: Response, db: SessionDep, form_data: OAuth2
return {"access_token": access_token, "token_type": "bearer"} return {"access_token": access_token, "token_type": "bearer"}
oauth2_scheme = OAuth2PasswordBearerWithCookie(tokenUrl="/api/login/token") @router.get("/profile", response_model=ProfileModel)
async def read_profile(current_user: Annotated[Profile, Depends(get_current_active_user)]):
return current_user
def get_current_user_from_token(db: SessionDep, token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
)
try:
payload = jwt.decode(
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
)
username: str = payload.get("sub")
print("username/email extracted is ", username)
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = get_profile(username=username, db=db)
if user is None:
raise credentials_exception
return user
+1 -1
View File
@@ -2,7 +2,6 @@ from typing import List
from fastapi import APIRouter, HTTPException, status from fastapi import APIRouter, HTTPException, status
from src.apis.utils import SessionDep
from src.core.log_conf import logger from src.core.log_conf import logger
from src.db.models.comic import Artist, Comic, Issue, Publisher from src.db.models.comic import Artist, Comic, Issue, Publisher
from src.db.repository.comics.artist import get_artist_details from src.db.repository.comics.artist import get_artist_details
@@ -13,6 +12,7 @@ from src.db.repository.comics.comic import (
list_comics, list_comics,
) )
from src.db.repository.comics.publisher import get_publisher_details from src.db.repository.comics.publisher import get_publisher_details
from src.db.session import SessionDep
from src.schema.comics.artist import ArtistCreation, ArtistResponse from src.schema.comics.artist import ArtistCreation, ArtistResponse
from src.schema.comics.artist_details import ArtistDetailResponse from src.schema.comics.artist_details import ArtistDetailResponse
from src.schema.comics.comic import ComicResponse from src.schema.comics.comic import ComicResponse
+1 -1
View File
@@ -1,8 +1,8 @@
from fastapi import APIRouter, status, HTTPException from fastapi import APIRouter, status, HTTPException
from sqlalchemy import select from sqlalchemy import select
from src.core.log_conf import logger from src.core.log_conf import logger
from src.apis.utils import SessionDep
from src.db.repository.media import create_new_mediaactor from src.db.repository.media import create_new_mediaactor
from src.db.session import SessionDep
from src.schema.media.actor import Actor, MediaActorResponse, get_actor_details from src.schema.media.actor import Actor, MediaActorResponse, get_actor_details
from src.db.models.media import MediaActor from src.db.models.media import MediaActor
@@ -1,8 +1,8 @@
from fastapi import APIRouter, status, HTTPException from fastapi import APIRouter, status, HTTPException
from sqlalchemy import select from sqlalchemy import select
from src.apis.utils import SessionDep
from src.db.models.media import MediaActorFile from src.db.models.media import MediaActorFile
from src.db.repository.media import delete_mediaactorfile from src.db.repository.media import delete_mediaactorfile
from src.db.session import SessionDep
from src.schema.media.actorfile import MediaActorFileResponse, get_actorfile_details from src.schema.media.actorfile import MediaActorFileResponse, get_actorfile_details
router = APIRouter() router = APIRouter()
+1 -1
View File
@@ -1,8 +1,8 @@
from fastapi import APIRouter, status, HTTPException, Depends from fastapi import APIRouter, status, HTTPException, Depends
from sqlalchemy import select, Sequence from sqlalchemy import select, Sequence
from src.core.log_conf import logger from src.core.log_conf import logger
from src.apis.utils import SessionDep
from src.db.repository.media import create_new_mediaactorfile, create_new_mediafile from src.db.repository.media import create_new_mediaactorfile, create_new_mediafile
from src.db.session import SessionDep
from src.schema.media.actor import MediaActorResponse from src.schema.media.actor import MediaActorResponse
from src.schema.media.actorfile import MediaActorFileResponse from src.schema.media.actorfile import MediaActorFileResponse
from src.schema.media.file import MediaFileResponse, Link, get_file_details, set_file from src.schema.media.file import MediaFileResponse, Link, get_file_details, set_file
+1 -1
View File
@@ -1,7 +1,7 @@
from typing import List from typing import List
from fastapi import APIRouter from fastapi import APIRouter
from src.apis.utils import SessionDep from src.db.session import SessionDep
from src.schema.tysc.sport import SportResponse from src.schema.tysc.sport import SportResponse
from src.db.models.tysc import Sport from src.db.models.tysc import Sport
+1 -1
View File
@@ -19,7 +19,7 @@ class Settings:
DATABASE_URL: str = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_SERVER}:{DB_PORT}/{DB_DBNAME}" DATABASE_URL: str = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_SERVER}:{DB_PORT}/{DB_DBNAME}"
SECRET_KEY: str = os.getenv("SECRET_KEY", "J6GOtcwC2NJI1l0VkHu20PacPFGTxpirBxWwynoHjsc=") SECRET_KEY: str = os.getenv("SECRET_KEY", "J6GOtcwC2NJI1l0VkHu20PacPFGTxpirBxWwynoHjsc=")
ALGORITHM = "HS256" ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 600 # in mins ACCESS_TOKEN_EXPIRE_MINUTES = 60*24*7 # one week in mins
settings = Settings() settings = Settings()
+142 -4
View File
@@ -1,17 +1,86 @@
from datetime import datetime import logging
from datetime import datetime, timezone
from datetime import timedelta from datetime import timedelta
from typing import Optional, Annotated, List
from typing import Dict
from typing import Optional from typing import Optional
from fastapi import HTTPException, Security
from fastapi import Request
from fastapi import status
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
from fastapi.security.utils import get_authorization_scheme_param
import bcrypt
from fastapi import Depends
from fastapi.security import SecurityScopes, OAuth2PasswordBearer, OAuth2
from pydantic import ValidationError
from src.core.config import settings from src.core.config import settings
from jose import jwt from jose import jwt, JWTError
from src.core.log_conf import logger
from src.db.models.admin import Profile
from src.db.repository.admin import get_profile
from src.db.session import SessionLocal
from src.schema.admin import TokenData, ProfileModel
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="/api/login/token",
scopes={"me": "read", "admin": "read"},
)
class OAuth2PasswordBearerWithCookie(OAuth2):
def __init__(
self,
tokenUrl: str,
scheme_name: Optional[str] = None,
scopes: Optional[Dict[str, str]] = None,
auto_error: bool = True,
):
if not scopes:
scopes = {}
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes})
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.cookies.get(
"access_token"
) # changed to accept access token from httpOnly Cookie
scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "bearer":
if self.auto_error:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
else:
return None
return param
def authenticate_user(username: str, password: str) -> Optional[Profile]:
with SessionLocal() as db:
user = get_profile(username=username, db=db)
print(user)
if not user:
return None
if bcrypt.checkpw(password.encode(), user.password.encode()):
print("User successful authenticated")
else:
logger.info("Authentication failed!")
return user
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy() to_encode = data.copy()
if expires_delta: if expires_delta:
expire = datetime.utcnow() + expires_delta expire = datetime.now(timezone.utc) + expires_delta
else: else:
expire = datetime.utcnow() + timedelta( expire = datetime.now(timezone.utc) + timedelta(
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
) )
to_encode.update({"exp": expire}) to_encode.update({"exp": expire})
@@ -19,3 +88,72 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM
) )
return encoded_jwt return encoded_jwt
async def get_current_user(security_scopes: SecurityScopes, token: Annotated[str, Depends(oauth2_scheme)]):
if security_scopes.scopes:
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
else:
authenticate_value = "Bearer"
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": authenticate_value},
)
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
username: str = payload.get("sub")
print("username/email extracted is ", username)
if username is None:
raise credentials_exception
scope: str = payload.get("scope", "")
token_scopes: List[str] = scope.split(" ")
token_data = TokenData(scopes=token_scopes, username=username)
except (JWTError, ValidationError):
raise credentials_exception
with SessionLocal() as db:
user = get_profile(username=token_data.username, db=db)
if user is None:
raise credentials_exception
for scope in security_scopes.scopes:
if scope not in token_scopes:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="not enough permissions",
headers={"WWW-Authenticate": authenticate_value},
)
return user
async def get_current_active_user(
current_user: Annotated[Profile, Security(get_current_user, scopes=["me"])],
) -> ProfileModel:
if not current_user.enabled:
raise HTTPException(status_code=400, detail="Inactive user")
user_model = ProfileModel(username=current_user.user_name, email=current_user.email,
first_name=current_user.first_name, last_name=current_user.last_name,
active=current_user.enabled)
return user_model
def get_current_user_from_token(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
)
try:
payload = jwt.decode(
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
)
username: str = payload.get("sub")
print("username/email extracted is ", username)
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
with SessionLocal() as db:
user = get_profile(username=username, db=db)
if user is None:
raise credentials_exception
return user
+2 -2
View File
@@ -1,10 +1,10 @@
from typing import AnyStr from typing import AnyStr, Optional
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from src.db.models.admin import Profile from src.db.models.admin import Profile
def get_profile(username: AnyStr, db: Session): def get_profile(username: AnyStr, db: Session) -> Optional[Profile]:
profile = db.query(Profile).filter(Profile.email == username).first() profile = db.query(Profile).filter(Profile.email == username).first()
return profile return profile
+16 -1
View File
@@ -1,5 +1,11 @@
from typing import List import uuid
from typing import List, Optional
from sqlalchemy.orm import Session
from src.core.log_conf import logger
from src.db.models.comic import Artist from src.db.models.comic import Artist
from src.schema.comics.artist import AddArtist
from src.schema.comics.artist_details import ArtistDetailResponse, ArtistWorktypeComicResponse, ArtistWorktypeIssueResponse from src.schema.comics.artist_details import ArtistDetailResponse, ArtistWorktypeComicResponse, ArtistWorktypeIssueResponse
from src.schema.comics.comic import ComicResponse from src.schema.comics.comic import ComicResponse
from src.schema.comics.worktype import WorktypeResponse from src.schema.comics.worktype import WorktypeResponse
@@ -29,3 +35,12 @@ def get_artist_details(artist: Artist) -> ArtistDetailResponse:
issue_works=issue_works, issue_works=issue_works,
) )
return response return response
def update_artist(add_artist: AddArtist, artist_id: str, db: Session) -> Artist:
logger.info("update artist")
artist: Optional[Artist] = db.get(Artist, artist_id)
artist.name = add_artist.name
db.add(artist)
db.commit()
db.refresh(artist)
return artist
+6 -2
View File
@@ -1,7 +1,8 @@
from typing import Generator from typing import Generator, Annotated
from fastapi import Depends
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker, Session
from src.core.config import settings from src.core.config import settings
@@ -10,6 +11,9 @@ engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(bind=engine) SessionLocal = sessionmaker(bind=engine)
def get_db() -> Generator: def get_db() -> Generator:
with SessionLocal() as db: with SessionLocal() as db:
yield db yield db
SessionDep: type[Session] = Annotated[Session, Depends(get_db)]
+14 -1
View File
@@ -1,4 +1,4 @@
from typing import Optional from typing import Optional, List
from pydantic import BaseModel from pydantic import BaseModel
@@ -6,3 +6,16 @@ from pydantic import BaseModel
class Token(BaseModel): class Token(BaseModel):
access_token: str access_token: str
token_type: str token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
scopes: List[str] = []
class ProfileModel(BaseModel):
username: str
email: str
first_name: str
last_name: str
active: bool
+4
View File
@@ -8,3 +8,7 @@ class ArtistCreation(BaseModel):
class ArtistResponse(BaseModel): class ArtistResponse(BaseModel):
id: str id: str
name: str name: str
class AddArtist(BaseModel):
id: str
name: str
@@ -23,8 +23,8 @@
<input type="text" required class="form-control" name="artist.link" value="{{artist_link}}" placeholder="Web link for artist here"> <input type="text" required class="form-control" name="artist.link" value="{{artist_link}}" placeholder="Web link for artist here">
</div> </div>
<div> <div>
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary" name="action" value="submit">Submit</button>
<button type="cancel" class="btn btn-primary">Cancel</button> <button type="cancel" class="btn btn-primary" name="action" value="cancel">Cancel</button>
</div> </div>
</form> </form>
</div> </div>
+6 -8
View File
@@ -1,31 +1,29 @@
from typing import AnyStr from typing import Optional
from fastapi import APIRouter, Request from fastapi import APIRouter, Request
from fastapi.security.utils import get_authorization_scheme_param
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from src.apis.utils import SessionDep
from src.apis.version1.admin import get_current_user_from_token
from src.db.models.admin import Permission, Profile from src.db.models.admin import Permission, Profile
from src.db.session import SessionDep
templates = Jinja2Templates(directory="src/templates") templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False, prefix="/admin") router = APIRouter(include_in_schema=False, prefix="/admin")
@router.get("/profiles") @router.get("/profiles")
def get_profiles(db: SessionDep, request: Request, msg: str | None = None): def get_profiles(db: SessionDep, request: Request, msg: Optional[str] = None):
profiles = db.query(Profile).all() profiles = db.query(Profile).all()
return templates.TemplateResponse("admin/profiles.html", {"request": request, "msg": msg, "profiles": profiles}) return templates.TemplateResponse("admin/profiles.html", {"request": request, "msg": msg, "profiles": profiles})
@router.get("/profiles/{profile_id}") @router.get("/profiles/{profile_id}")
def comic_details(profile_id: AnyStr, request: Request, db: SessionDep): def comic_details(profile_id: str, request: Request, db: SessionDep):
profile = db.get(Profile, profile_id) profile = db.get(Profile, profile_id)
return templates.TemplateResponse("admin/profile_detail.html", {"request": request, "profile":profile}) return templates.TemplateResponse("admin/profile_detail.html", {"request": request, "profile":profile})
@router.get("/permissions") @router.get("/permissions")
def get_permissions(db: SessionDep, request: Request, msg: str | None = None): def get_permissions(db: SessionDep, request: Request, msg: Optional[str] = None):
permissions = db.query(Permission).all() permissions = db.query(Permission).all()
return templates.TemplateResponse("admin/permissions.html", {"request": request, "msg": msg, "permissions": permissions}) return templates.TemplateResponse("admin/permissions.html", {"request": request, "msg": msg, "permissions": permissions})
@router.get("/permissions/{permission_id}") @router.get("/permissions/{permission_id}")
def artist_detail(permission_id: AnyStr, request: Request, db: SessionDep): def artist_detail(permission_id: str, request: Request, db: SessionDep):
permission= db.get(Permission, str(permission_id)) permission= db.get(Permission, str(permission_id))
return templates.TemplateResponse("comic/permission_detail.html", {"request": request, "permission": permission}) return templates.TemplateResponse("comic/permission_detail.html", {"request": request, "permission": permission})
+8 -7
View File
@@ -1,11 +1,12 @@
from src.apis.version1.admin import login_for_access_token # from src.apis.version1.admin import login_for_access_token
from src.db.session import get_db from fastapi.security import OAuth2PasswordRequestForm
from fastapi import APIRouter
from fastapi import Depends 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 HTTPException
from fastapi import Request from fastapi import Request
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
from src.webapps.auth.forms import LoginForm from src.webapps.auth.forms import LoginForm
@@ -19,14 +20,14 @@ def login(request: Request):
@router.post("/login/") @router.post("/login/")
async def login(request: Request, db: Session = Depends(get_db)): async def login(request: Request):
form = LoginForm(request) form = LoginForm(request)
await form.load_data() await form.load_data()
if await form.is_valid(): if await form.is_valid():
try: try:
form.__dict__.update(msg="Login Successful :)") form.__dict__.update(msg="Login Successful :)")
response = templates.TemplateResponse("auth/login.html", form.__dict__) response = templates.TemplateResponse("auth/login.html", form.__dict__)
login_for_access_token(response=response, form_data=form, db=db) login_for_token_cookie(response=response, form_data=form)
return response return response
except HTTPException: except HTTPException:
form.__dict__.update(msg="") form.__dict__.update(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=}"
+11 -5
View File
@@ -1,13 +1,17 @@
from fastapi import APIRouter, Request, status from fastapi import APIRouter, Request, status, Form
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from src.apis.utils import SessionDep
from src.db.models.comic import Artist from src.db.models.comic import Artist
from typing import AnyStr 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.db.repository.comic import create_new_worktype, update_worktype
from src.main import logger 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.schema.comics.worktype import AddWorkType
#from src.webapps.comic.forms import AddWorktypeForm #from src.webapps.comic.forms import AddWorktypeForm
@@ -31,13 +35,15 @@ def edit_artist(db: SessionDep, request: Request, artist_id: str):
return templates.TemplateResponse("comic/artist_edit.html", {"request": request, "artist_name": artist.name, "artist_link": artist.weblink}) return templates.TemplateResponse("comic/artist_edit.html", {"request": request, "artist_name": artist.name, "artist_link": artist.weblink})
@router.post("/artist/edit/{artist_id}") @router.post("/artist/edit/{artist_id}")
async def edit_artist(request: Request, db: SessionDep, artist_id: str): async def edit_artist(request: Request, db: SessionDep, artist_id: str, action: str = Form(...), artist_name: str = Form(...), artist_link: str = Form(...)):
form = AddArtistForm(request) 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() await form.load_data()
if form.is_valid(): if form.is_valid():
try: try:
artist = AddArtist(**form.__dict__) artist = AddArtist(**form.__dict__)
artist = update_artist(artist=artist, artist_id=artist_id, db=db) 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) return RedirectResponse(f"/comic/artists/{artist.id}", status_code=status.HTTP_303_SEE_OTHER)
except Exception as e: except Exception as e:
print(e) print(e)
+2 -2
View File
@@ -2,11 +2,11 @@ from fastapi import APIRouter, Form, Request, status
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from src.apis.utils import SessionDep
from src.db.models.comic import Comic, Publisher, Issue from src.db.models.comic import Comic, Publisher, Issue
from typing import AnyStr from typing import AnyStr
from src.core.log_conf import logger from src.core.log_conf import logger
from src.db.repository.comics.comic import update_comic from src.db.repository.comics.comic import update_comic
from src.db.session import SessionDep
from src.schema.comics.comic import ComicSchema from src.schema.comics.comic import ComicSchema
from src.webapps.comic.forms.comic import ValidateComicForm from src.webapps.comic.forms.comic import ValidateComicForm
@@ -50,7 +50,7 @@ def edit_comic(db: SessionDep, request: Request, comic_id: str):
@router.post("/comic/edit/{comic_id}") @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)): 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": if action == "cancel":
return RedirectResponse(f"/comic/comics/{comic_id}", status_code=status.HTTP_303_SEE_OTHER) return RedirectResponse(f"/comic/comics/{comic_id}", status_code=status.HTTP_303_SEE_OTHER)
form = ValidateComicForm(request, comic_id, completed, current_order) form = ValidateComicForm(request, comic_id, completed, current_order)
logger.info(f"request: {repr(request)}") logger.info(f"request: {repr(request)}")
await form.load_data() await form.load_data()
@@ -2,11 +2,11 @@ from fastapi import APIRouter, Request, status
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from src.apis.utils import SessionDep
from src.db.models.comic import WorkType from src.db.models.comic import WorkType
from typing import AnyStr from typing import AnyStr
from src.db.repository.comics.worktype import create_new_worktype, update_worktype 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.main import logger
from src.schema.comics.worktype import AddWorkType from src.schema.comics.worktype import AddWorkType
from src.webapps.comic.forms.worktype import AddWorktypeForm from src.webapps.comic.forms.worktype import AddWorktypeForm
+1 -4
View File
@@ -2,11 +2,8 @@ from typing import AnyStr
from fastapi import APIRouter, Request from fastapi import APIRouter, Request
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from sqlalchemy import or_
from src.apis.utils import SessionDep
from src.db.models.media import MediaActor from src.db.models.media import MediaActor
from src.core.log_conf import logger from src.db.session import SessionDep
templates = Jinja2Templates(directory="src/templates") templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False, prefix="/media") router = APIRouter(include_in_schema=False, prefix="/media")
+4 -5
View File
@@ -5,11 +5,11 @@ from fastapi.security.utils import get_authorization_scheme_param
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from sqlalchemy import or_ from sqlalchemy import or_
from src.apis.utils import SessionDep from src.core.security import get_current_user_from_token
from src.apis.version1.admin import get_current_user_from_token
from src.db.models.admin import Profile from src.db.models.admin import Profile
from src.db.models.media import MediaFile, MediaActor from src.db.models.media import MediaFile
from src.core.log_conf import logger from src.core.log_conf import logger
from src.db.session import SessionDep
templates = Jinja2Templates(directory="src/templates") templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False, prefix="/media") router = APIRouter(include_in_schema=False, prefix="/media")
@@ -37,7 +37,7 @@ def get_mediafiles(db: SessionDep, request: Request, msg: str | None = None):
try: try:
token = request.cookies.get("access_token") token = request.cookies.get("access_token")
scheme, param = get_authorization_scheme_param(token) # scheme will hold "Bearer" and param will hold actual token value 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, db=db) current_user: Profile = get_current_user_from_token(token=param)
return templates.TemplateResponse("media/files.html", {"request": request, "msg": msg, "mediafiles": mediafiles}) return templates.TemplateResponse("media/files.html", {"request": request, "msg": msg, "mediafiles": mediafiles})
except Exception as e: except Exception as e:
print(e) print(e)
@@ -54,4 +54,3 @@ def file_details(file_id: AnyStr, request: Request, db: SessionDep):
def edit_file(db: SessionDep, request: Request, file_id: str): def edit_file(db: SessionDep, request: Request, file_id: str):
media_file = db.get(MediaFile, file_id) media_file = db.get(MediaFile, file_id)
return templates.TemplateResponse("media/file_detail.html", {"request": request, "mediafile":media_file}) return templates.TemplateResponse("media/file_detail.html", {"request": request, "mediafile":media_file})
+2 -2
View File
@@ -4,11 +4,11 @@ from fastapi import APIRouter, Request, status, responses
from fastapi.security.utils import get_authorization_scheme_param from fastapi.security.utils import get_authorization_scheme_param
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from src.apis.utils import SessionDep
from src.db.models.media import MediaVideo from src.db.models.media import MediaVideo
from src.db.repository.media import create_new_video from src.db.repository.media import create_new_video
from src.apis.version1.admin import get_current_user_from_token #from src.apis.version1.admin import get_current_user_from_token
from src.db.models.admin import Profile from src.db.models.admin import Profile
from src.db.session import SessionDep
from src.schema.media.video import AddLink from src.schema.media.video import AddLink
from src.webapps.media.forms import AddLinkForm from src.webapps.media.forms import AddLinkForm