import datetime from typing import Annotated from fastapi import APIRouter, File, Form, HTTPException, Request from sqlalchemy import func, select from sqlalchemy.exc import IntegrityError from starlette import status from starlette.responses import RedirectResponse from allmende_payment_system import types from allmende_payment_system.api.dependencies import SessionDep, UserDep from allmende_payment_system.models import ( Account, Area, Permission, Product, ProductDetails, Transaction, User, UserGroup, ) from allmende_payment_system.tools import get_jinja_renderer admin_router = APIRouter(prefix="/admin") # USERS @admin_router.get("/users") async def user_list(request: Request, session: SessionDep, user: UserDep): if not user.has_permission("user", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") users = session.scalars(select(User)).all() groups = session.scalars(select(UserGroup)).all() templates = get_jinja_renderer() return templates.TemplateResponse( request, "users.html.jinja", context={"users": users, "all_groups": groups}, ) @admin_router.post("/users/{user_id}/add_group") async def user_add_group( request: Request, session: SessionDep, loggend_in_user: UserDep, user_id: int, group_id: Annotated[int, Form()], ): if not loggend_in_user.has_permission("user", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") group = session.execute( select(UserGroup).where(UserGroup.id == group_id) ).scalar_one() user = session.execute(select(User).where(User.id == user_id)).scalar_one() user.user_groups.append(group) return RedirectResponse(url="/admin/users", status_code=status.HTTP_303_SEE_OTHER) @admin_router.get("/users/{user_id}/remove_group/{group_id}") async def user_remove_group( request: Request, session: SessionDep, loggend_in_user: UserDep, user_id: int, group_id: int, ): if not loggend_in_user.has_permission("user", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") group = session.execute( select(UserGroup).where(UserGroup.id == group_id) ).scalar_one() user = session.execute(select(User).where(User.id == user_id)).scalar_one() print(user) user.user_groups.remove(group) return RedirectResponse(url="/admin/users", status_code=status.HTTP_303_SEE_OTHER) # GROUPS @admin_router.get("/groups") async def group_list(request: Request, session: SessionDep, user: UserDep): if not user.has_permission("user", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") groups = session.scalars(select(UserGroup)).all() templates = get_jinja_renderer() return templates.TemplateResponse( request, "groups.html.jinja", context={"groups": groups}, ) @admin_router.post("/groups/{group_id}/add_permission") async def group_add_permission( request: Request, session: SessionDep, user: UserDep, group_id: int, permission: Annotated[str, Form()], ): if not user.has_permission("user", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") scope_action = permission.split(":") if len(scope_action) != 2: raise HTTPException( status_code=400, detail="Permission must be in the format 'scope:action'" ) permission = Permission(scope=scope_action[0], action=scope_action[1]) group = session.execute( select(UserGroup).where(UserGroup.id == group_id) ).scalar_one() session.add(permission) group.permissions.append(permission) return RedirectResponse(url="/admin/groups", status_code=status.HTTP_303_SEE_OTHER) @admin_router.get("/groups/{group_id}/remove_permission/{permission_id}") async def group_remove_permission( request: Request, session: SessionDep, user: UserDep, group_id: int, permission_id: int, ): if not user.has_permission("user", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") permission = session.execute( select(Permission).where(Permission.id == permission_id) ).scalar_one() group = session.execute( select(UserGroup).where(UserGroup.id == group_id) ).scalar_one() group.permissions.remove(permission) session.delete(permission) return RedirectResponse(url="/admin/groups", status_code=status.HTTP_303_SEE_OTHER) @admin_router.post("/groups/create") async def create_group( request: Request, session: SessionDep, user: UserDep, group_data: Annotated[types.UserGroup, Form()], ): if not user.has_permission("user", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") group = UserGroup(name=group_data.name, description=group_data.description) session.add(group) return RedirectResponse(url="/admin/groups", status_code=status.HTTP_303_SEE_OTHER) @admin_router.get("/groups/{group_id}/delete") async def delete_group( request: Request, session: SessionDep, user: UserDep, group_id: int, ): if not user.has_permission("user", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") group = session.execute( select(UserGroup).where(UserGroup.id == group_id) ).scalar_one() session.delete(group) return RedirectResponse(url="/admin/groups", status_code=status.HTTP_303_SEE_OTHER) # PRODUCTS @admin_router.get("/products") async def get_products( request: Request, session: SessionDep, user: UserDep, ): products = session.scalars( select(Product).order_by(Product.area_id, Product.name) ).all() templates = get_jinja_renderer() return templates.TemplateResponse( request, "products.html.jinja", context={"products": products}, ) @admin_router.get("/products/edit/{product_id}") async def edit_product_get( request: Request, session: SessionDep, user: UserDep, product_id: int ): product = session.execute(select(Product).where(Product.id == product_id)).scalar() areas = session.scalars(select(Area)).all() templates = get_jinja_renderer() return templates.TemplateResponse( request, "product_edit.html.jinja", context={ "product": product, "edit_mode": True, "areas": areas, }, ) @admin_router.post("/products/edit/{product_id}") async def edit_product_post( request: Request, session: SessionDep, user: UserDep, product_id: int, product_data: Annotated[types.Product, Form()], ): if not user.has_permission("product", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") product = session.execute( select(Product).where(Product.id == product_id) ).scalar_one() new_details = ProductDetails( valid_from=func.now(), ) for field_name, data in product_data.model_dump().items(): setattr(new_details, field_name, data) product.details_history.append(new_details) session.flush() return RedirectResponse( url="/admin/products", status_code=status.HTTP_303_SEE_OTHER ) @admin_router.get("/products/new") async def new_product_get( request: Request, session: SessionDep, user: UserDep, ): if not user.has_permission("product", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") areas = session.scalars(select(Area)).all() templates = get_jinja_renderer() return templates.TemplateResponse( request, "product_edit.html.jinja", context={"edit_mode": False, "areas": areas}, ) @admin_router.post("/products/new") async def new_product_post( request: Request, session: SessionDep, user: UserDep, product_data: Annotated[types.Product, Form()], ): if not user.has_permission("product", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") product = Product(area_id=product_data.area_id) session.add(product) session.flush() product_details = ProductDetails() product.details_history.append(product_details) for field_name, data in product_data.model_dump().items(): setattr(product_details, field_name, data) return RedirectResponse( url="/admin/products", status_code=status.HTTP_303_SEE_OTHER ) @admin_router.get("/accounts") async def get_accounts( request: Request, session: SessionDep, user: UserDep, ): if not user.has_permission("account", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") templates = get_jinja_renderer() accounts = session.scalars(select(Account)).all() users = session.scalars(select(User)).all() return templates.TemplateResponse( request, "accounts.html.jinja", context={"accounts": accounts, "users": users}, ) @admin_router.post("/accounts/new") async def create_account( request: Request, session: SessionDep, user: UserDep, account_name: Annotated[str, Form()], ): if not user.has_permission("account", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") account = Account(name=account_name) session.add(account) try: session.flush() except IntegrityError as e: session.rollback() raise HTTPException( status_code=400, detail="Account with this name already exists" ) from e return RedirectResponse( url="/admin/accounts", status_code=status.HTTP_303_SEE_OTHER ) @admin_router.post("/accounts/{account_id}/add_user") async def add_user_to_account( request: Request, session: SessionDep, user: UserDep, account_id: int, user_id: Annotated[int, Form()], ): if not user.has_permission("account", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") account = session.execute( select(Account).where(Account.id == account_id) ).scalar_one() user_to_add = session.execute(select(User).where(User.id == user_id)).scalar_one() account.users.append(user_to_add) return RedirectResponse( url="/admin/accounts", status_code=status.HTTP_303_SEE_OTHER ) @admin_router.post("/accounts/{account_id}/add_balance") async def add_balance_to_account( request: Request, session: SessionDep, user: UserDep, account_id: int, amount: Annotated[float, Form()], ): if not user.has_permission("account", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") account = session.execute( select(Account).where(Account.id == account_id) ).scalar_one() account.transactions.append(Transaction(type="deposit", total_amount=amount)) return RedirectResponse( url="/admin/accounts", status_code=status.HTTP_303_SEE_OTHER ) @admin_router.get("/accounts/{account_id}/details") async def view_account_details( request: Request, session: SessionDep, user: UserDep, account_id: int, ): if not user.has_permission("account", "edit"): raise HTTPException(status_code=403, detail="Insufficient permissions") account = session.execute( select(Account).where(Account.id == account_id) ).scalar_one() templates = get_jinja_renderer() return templates.TemplateResponse( request, "account_details.html.jinja", context={"account": account}, )