diff --git a/frontend/src/components/modals/CancelOrderModal.tsx b/frontend/src/components/modals/CancelOrderModal.tsx index ecb6af5a..40cdcba2 100644 --- a/frontend/src/components/modals/CancelOrderModal.tsx +++ b/frontend/src/components/modals/CancelOrderModal.tsx @@ -11,9 +11,7 @@ import { Label } from "@/components/ui/label"; import { paths } from "@/gen/api"; import { useAlertQueue } from "@/hooks/useAlertQueue"; import { useAuthentication } from "@/hooks/useAuth"; - -type Order = - paths["/orders/user-orders"]["get"]["responses"][200]["content"]["application/json"][0]; +import type { OrderWithProduct } from "@/lib/types/orders"; type RefundRequest = paths["/stripe/refunds/{order_id}"]["put"]["requestBody"]["content"]["application/json"]; @@ -21,8 +19,8 @@ type RefundRequest = interface CancelOrderModalProps { isOpen: boolean; onOpenChange: (open: boolean) => void; - order: Order; - onOrderUpdate: (updatedOrder: Order) => void; + order: OrderWithProduct; + onOrderUpdate: (updatedOrder: OrderWithProduct) => void; } const CancelOrderModal: React.FC = ({ @@ -90,7 +88,7 @@ const CancelOrderModal: React.FC = ({ try { const { data, error } = await client.PUT("/stripe/refunds/{order_id}", { - params: { path: { order_id: order.id } }, + params: { path: { order_id: order.order.id } }, body: cancellation, }); @@ -99,7 +97,10 @@ const CancelOrderModal: React.FC = ({ console.error("Error canceling order:", error); } else { addAlert("Order successfully canceled", "success"); - onOrderUpdate(data); + onOrderUpdate({ + order: data, + product: order.product, + }); } onOpenChange(false); diff --git a/frontend/src/components/modals/EditAddressModal.tsx b/frontend/src/components/modals/EditAddressModal.tsx index 8fd2c49d..af664fde 100644 --- a/frontend/src/components/modals/EditAddressModal.tsx +++ b/frontend/src/components/modals/EditAddressModal.tsx @@ -9,18 +9,15 @@ import { } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { paths } from "@/gen/api"; import { useAlertQueue } from "@/hooks/useAlertQueue"; import { useAuthentication } from "@/hooks/useAuth"; - -type Order = - paths["/orders/user-orders"]["get"]["responses"][200]["content"]["application/json"][0]; +import type { OrderWithProduct } from "@/lib/types/orders"; interface EditAddressModalProps { isOpen: boolean; onOpenChange: (open: boolean) => void; - order: Order; - onOrderUpdate: (updatedOrder: Order) => void; + order: OrderWithProduct; + onOrderUpdate: (updatedOrder: OrderWithProduct) => void; } const EditAddressModal: React.FC = ({ @@ -32,13 +29,13 @@ const EditAddressModal: React.FC = ({ const { client } = useAuthentication(); const { addAlert, addErrorAlert } = useAlertQueue(); const [address, setAddress] = useState({ - shipping_name: order.shipping_name || "", - shipping_address_line1: order.shipping_address_line1 || "", - shipping_address_line2: order.shipping_address_line2 || "", - shipping_city: order.shipping_city || "", - shipping_state: order.shipping_state || "", - shipping_postal_code: order.shipping_postal_code || "", - shipping_country: order.shipping_country || "", + shipping_name: order.order.shipping_name || "", + shipping_address_line1: order.order.shipping_address_line1 || "", + shipping_address_line2: order.order.shipping_address_line2 || "", + shipping_city: order.order.shipping_city || "", + shipping_state: order.order.shipping_state || "", + shipping_postal_code: order.order.shipping_postal_code || "", + shipping_country: order.order.shipping_country || "", }); const handleInputChange = (e: React.ChangeEvent) => { @@ -50,9 +47,9 @@ const EditAddressModal: React.FC = ({ e.preventDefault(); try { const { data, error } = await client.PUT( - "/orders/update-order-address/{order_id}", + "/orders/{order_id}/shipping-address", { - params: { path: { order_id: order.id } }, + params: { path: { order_id: order.order.id } }, body: address, }, ); @@ -62,7 +59,10 @@ const EditAddressModal: React.FC = ({ console.error("Error updating address:", error); } else { addAlert("Delivery address updated", "success"); - onOrderUpdate(data); + onOrderUpdate({ + order: data, + product: order.product, + }); } onOpenChange(false); diff --git a/frontend/src/components/orders/OrderCard.tsx b/frontend/src/components/orders/OrderCard.tsx index df65f7b4..5295c766 100644 --- a/frontend/src/components/orders/OrderCard.tsx +++ b/frontend/src/components/orders/OrderCard.tsx @@ -8,16 +8,10 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import type { paths } from "@/gen/api"; +import type { OrderWithProduct } from "@/lib/types/orders"; import { formatPrice } from "@/lib/utils/formatNumber"; import { normalizeStatus } from "@/lib/utils/formatString"; -type OrderWithProduct = - paths["/orders/user-orders-with-products"]["get"]["responses"][200]["content"]["application/json"][0]; - -type Order = - paths["/orders/user-orders"]["get"]["responses"][200]["content"]["application/json"][0]; - enum OrderStatus { PREORDER = -1, PROCESSING = 0, @@ -60,7 +54,7 @@ const canModifyStatuses = [ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({ orderWithProduct: initialOrderWithProduct, }) => { - const [orderWithProduct, setOrderWithProduct] = useState( + const [orderWithProduct, setOrderWithProduct] = useState( initialOrderWithProduct, ); const { order, product } = orderWithProduct; @@ -92,8 +86,8 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({ const unitPrice = order.price_amount / order.quantity; - const handleOrderUpdate = (updatedOrder: Order) => { - setOrderWithProduct((prev) => ({ ...prev, order: updatedOrder })); + const handleOrderUpdate = (updatedOrder: OrderWithProduct) => { + setOrderWithProduct(updatedOrder); }; const canModifyOrder = () => { @@ -281,13 +275,13 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({ diff --git a/frontend/src/components/pages/Orders.tsx b/frontend/src/components/pages/Orders.tsx index bac7a0de..0a51f493 100644 --- a/frontend/src/components/pages/Orders.tsx +++ b/frontend/src/components/pages/Orders.tsx @@ -4,15 +4,12 @@ import { useNavigate } from "react-router-dom"; import OrderCard from "@/components/orders/OrderCard"; import Spinner from "@/components/ui/Spinner"; import { Button } from "@/components/ui/button"; -import type { paths } from "@/gen/api"; import { useAlertQueue } from "@/hooks/useAlertQueue"; import { useAuthentication } from "@/hooks/useAuth"; import { ApiError } from "@/lib/types/api"; +import type { OrderWithProduct } from "@/lib/types/orders"; import ROUTES from "@/lib/types/routes"; -type OrderWithProduct = - paths["/orders/user-orders-with-products"]["get"]["responses"][200]["content"]["application/json"][number]; - const OrdersPage: React.FC = () => { const navigate = useNavigate(); const { api, currentUser, isAuthenticated, isLoading } = useAuthentication(); @@ -25,9 +22,10 @@ const OrdersPage: React.FC = () => { if (isAuthenticated && currentUser) { setLoadingOrders(true); try { - const { data, error } = await api.client.GET( - "/orders/user-orders-with-products", - ); + const { data, error } = await api.client.GET("/orders/me", { + params: { query: { include_products: true } }, + }); + if (error) { const apiError = error as ApiError; if (apiError.status === 500) { @@ -38,7 +36,7 @@ const OrdersPage: React.FC = () => { } setOrders([]); } else { - setOrders(data || []); + setOrders(data as OrderWithProduct[]); } } finally { setLoadingOrders(false); diff --git a/frontend/src/gen/api.ts b/frontend/src/gen/api.ts index 543e1db5..d35074df 100644 --- a/frontend/src/gen/api.ts +++ b/frontend/src/gen/api.ts @@ -652,24 +652,7 @@ export interface paths { patch?: never; trace?: never; }; - "/orders/user-orders": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get User Orders */ - get: operations["get_user_orders_orders_user_orders_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/orders/order/{order_id}": { + "/orders/{order_id}": { parameters: { query?: never; header?: never; @@ -677,24 +660,7 @@ export interface paths { cookie?: never; }; /** Get Order */ - get: operations["get_order_orders_order__order_id__get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/orders/order-with-product/{order_id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get Order With Product */ - get: operations["get_order_with_product_orders_order_with_product__order_id__get"]; + get: operations["get_order_orders__order_id__get"]; put?: never; post?: never; delete?: never; @@ -703,15 +669,15 @@ export interface paths { patch?: never; trace?: never; }; - "/orders/user-orders-with-products": { + "/orders/me": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Get User Orders With Products */ - get: operations["get_user_orders_with_products_orders_user_orders_with_products_get"]; + /** Get User Orders */ + get: operations["get_user_orders_orders_me_get"]; put?: never; post?: never; delete?: never; @@ -720,7 +686,7 @@ export interface paths { patch?: never; trace?: never; }; - "/orders/update-order-address/{order_id}": { + "/orders/{order_id}/shipping-address": { parameters: { query?: never; header?: never; @@ -728,8 +694,8 @@ export interface paths { cookie?: never; }; get?: never; - /** Update Order Address */ - put: operations["update_order_address_orders_update_order_address__order_id__put"]; + /** Update Order Shipping Address */ + put: operations["update_order_shipping_address_orders__order_id__shipping_address_put"]; post?: never; delete?: never; options?: never; @@ -1777,7 +1743,7 @@ export interface components { /** OrderWithProduct */ OrderWithProduct: { order: components["schemas"]["Order"]; - product: components["schemas"]["ProductInfo"]; + product: components["schemas"]["ProductInfo"] | null; }; /** ProductInfo */ ProductInfo: { @@ -3296,29 +3262,11 @@ export interface operations { }; }; }; - get_user_orders_orders_user_orders_get: { + get_order_orders__order_id__get: { parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Order"][]; - }; + query?: { + include_product?: boolean; }; - }; - }; - get_order_orders_order__order_id__get: { - parameters: { - query?: never; header?: never; path: { order_id: string; @@ -3333,7 +3281,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Order"]; + "application/json": components["schemas"]["OrderWithProduct"]; }; }; /** @description Validation Error */ @@ -3347,13 +3295,13 @@ export interface operations { }; }; }; - get_order_with_product_orders_order_with_product__order_id__get: { + get_user_orders_orders_me_get: { parameters: { - query?: never; - header?: never; - path: { - order_id: string; + query?: { + include_products?: boolean; }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; @@ -3364,7 +3312,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["OrderWithProduct"]; + "application/json": components["schemas"]["OrderWithProduct"][]; }; }; /** @description Validation Error */ @@ -3378,27 +3326,7 @@ export interface operations { }; }; }; - get_user_orders_with_products_orders_user_orders_with_products_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["OrderWithProduct"][]; - }; - }; - }; - }; - update_order_address_orders_update_order_address__order_id__put: { + update_order_shipping_address_orders__order_id__shipping_address_put: { parameters: { query?: never; header?: never; diff --git a/frontend/src/lib/types/orders.ts b/frontend/src/lib/types/orders.ts new file mode 100644 index 00000000..1bcdc5d7 --- /dev/null +++ b/frontend/src/lib/types/orders.ts @@ -0,0 +1,17 @@ +import type { paths } from "@/gen/api"; + +export type Order = + paths["/orders/{order_id}"]["get"]["responses"][200]["content"]["application/json"]; + +export type OrderWithProduct = + paths["/orders/{order_id}"]["get"]["responses"][200]["content"]["application/json"] & { + product: ProductInfo; + }; + +export type ProductInfo = { + id: string; + name: string; + description: string | null; + images: string[]; + metadata: Record; +}; diff --git a/store/app/model.py b/store/app/model.py index 19616431..1d568a8d 100644 --- a/store/app/model.py +++ b/store/app/model.py @@ -572,6 +572,7 @@ def create(cls, user_id: str, listing_id: str, is_upvote: bool) -> Self: class Order(StoreBaseModel): """Tracks completed user orders through Stripe.""" + id: str user_id: str listing_id: str user_email: str diff --git a/store/app/routers/listings.py b/store/app/routers/listings.py index ef795cbd..df92ec70 100644 --- a/store/app/routers/listings.py +++ b/store/app/routers/listings.py @@ -2,7 +2,7 @@ import asyncio import logging -from typing import Annotated, List, Literal +from typing import Annotated, Literal from fastapi import ( APIRouter, @@ -283,7 +283,7 @@ async def add_listing( inventory_quantity: str | None = Form(None), preorder_deposit_amount: str | None = Form(None), preorder_release_date: str | None = Form(None), - photos: List[UploadFile] = File(None), + photos: list[UploadFile] = File(None), ) -> NewListingResponse: try: logger.info("Starting to process add listing request") diff --git a/store/app/routers/orders.py b/store/app/routers/orders.py index f1c9b363..b75290e0 100644 --- a/store/app/routers/orders.py +++ b/store/app/routers/orders.py @@ -1,7 +1,6 @@ """Defines the router endpoints for handling Orders.""" from logging import getLogger -from typing import Annotated, Dict, List from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel @@ -11,7 +10,10 @@ from store.app.db import Crud from store.app.model import Order, User from store.app.routers import stripe -from store.app.routers.users import get_session_user_with_read_permission +from store.app.security.user import ( + get_session_user_with_read_permission, + get_session_user_with_write_permission, +) router = APIRouter() @@ -22,47 +24,28 @@ class ProductInfo(BaseModel): id: str name: str description: str | None - images: List[str] - metadata: Dict[str, str] + images: list[str] + metadata: dict[str, str] class OrderWithProduct(BaseModel): order: Order - product: ProductInfo + product: ProductInfo | None -@router.get("/user-orders", response_model=List[Order]) -async def get_user_orders( - user: User = Depends(get_session_user_with_read_permission), crud: Crud = Depends(Crud.get) -) -> List[Order]: - try: - orders = await crud.get_orders_by_user_id(user.id) - return orders - except ItemNotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No orders found for this user") - - -@router.get("/order/{order_id}", response_model=Order) +@router.get("/{order_id}", response_model=OrderWithProduct) async def get_order( order_id: str, + include_product: bool = False, user: User = Depends(get_session_user_with_read_permission), crud: Crud = Depends(Crud.get), -) -> Order: +) -> Order | OrderWithProduct: order = await crud.get_order(order_id) if order is None or order.user_id != user.id: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Order not found") - return order - -@router.get("/order-with-product/{order_id}", response_model=OrderWithProduct) -async def get_order_with_product( - order_id: str, - crud: Annotated[Crud, Depends(Crud.get)], - user: User = Depends(get_session_user_with_read_permission), -) -> OrderWithProduct: - order = await crud.get_order(order_id) - if order is None or order.user_id != user.id: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Order not found") + if not include_product: + return order if order.stripe_product_id is None: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Order has no associated product") @@ -71,17 +54,23 @@ async def get_order_with_product( return OrderWithProduct(order=order, product=ProductInfo(**product)) -@router.get("/user-orders-with-products", response_model=List[OrderWithProduct]) -async def get_user_orders_with_products( - crud: Annotated[Crud, Depends(Crud.get)], +@router.get("/me", response_model=list[OrderWithProduct]) +async def get_user_orders( + include_products: bool = False, user: User = Depends(get_session_user_with_read_permission), -) -> List[OrderWithProduct]: + crud: Crud = Depends(Crud.get), +) -> list[OrderWithProduct]: try: orders = await crud.get_orders_by_user_id(user.id) + + if not include_products: + return [OrderWithProduct(order=order, product=None) for order in orders] + orders_with_products = [] for order in orders: try: if order.stripe_product_id is None: + orders_with_products.append(OrderWithProduct(order=order, product=None)) continue product = await stripe.get_product(order.stripe_product_id, crud) orders_with_products.append(OrderWithProduct(order=order, product=ProductInfo(**product))) @@ -89,6 +78,7 @@ async def get_user_orders_with_products( logger.error( "Error processing order", extra={"order_id": order.id, "error": str(e), "user_id": user.id} ) + orders_with_products.append(OrderWithProduct(order=order, product=None)) continue return orders_with_products except ItemNotFoundError: @@ -110,11 +100,11 @@ class UpdateOrderAddressRequest(BaseModel): shipping_country: str -@router.put("/update-order-address/{order_id}", response_model=Order) -async def update_order_address( +@router.put("/{order_id}/shipping-address", response_model=Order) +async def update_order_shipping_address( order_id: str, address_update: UpdateOrderAddressRequest, - user: User = Depends(get_session_user_with_read_permission), + user: User = Depends(get_session_user_with_write_permission), crud: Crud = Depends(Crud.get), ) -> Order: order = await crud.get_order(order_id) diff --git a/store/app/routers/stripe.py b/store/app/routers/stripe.py index c20da475..851d9712 100644 --- a/store/app/routers/stripe.py +++ b/store/app/routers/stripe.py @@ -4,7 +4,7 @@ import time from datetime import datetime from enum import Enum -from typing import Annotated, Any, Dict, Literal +from typing import Annotated, Literal import stripe from fastapi import APIRouter, Body, Depends, HTTPException, Request, status @@ -113,7 +113,7 @@ async def refund_payment_intent( @router.post("/webhook") -async def stripe_webhook(request: Request, crud: Crud = Depends(Crud.get)) -> Dict[str, str]: +async def stripe_webhook(request: Request, crud: Crud = Depends(Crud.get)) -> dict[str, str]: """Handle direct account webhooks (non-Connect events).""" payload = await request.body() sig_header = request.headers.get("stripe-signature") @@ -137,7 +137,7 @@ async def stripe_webhook(request: Request, crud: Crud = Depends(Crud.get)) -> Di @router.post("/connect/webhook") -async def stripe_connect_webhook(request: Request, crud: Crud = Depends(Crud.get)) -> Dict[str, str]: +async def stripe_connect_webhook(request: Request, crud: Crud = Depends(Crud.get)) -> dict[str, str]: """Handle Connect account webhooks.""" payload = await request.body() sig_header = request.headers.get("stripe-signature") @@ -209,7 +209,7 @@ async def stripe_connect_webhook(request: Request, crud: Crud = Depends(Crud.get raise HTTPException(status_code=500, detail=str(e)) -async def handle_checkout_session_completed(session: Dict[str, Any], crud: Crud) -> None: +async def handle_checkout_session_completed(session: stripe.checkout.Session, crud: Crud) -> None: logger.info("Processing checkout session: %s", session["id"]) try: # Retrieve full session details from the connected account @@ -278,10 +278,6 @@ async def handle_checkout_session_completed(session: Dict[str, Any], crud: Crud) raise -async def notify_payment_failed(session: Dict[str, Any]) -> None: - logger.warning("Payment failed for session: %s", session["id"]) - - def support_affirm_payment(listing: Listing) -> bool: if listing.price_amount is None: return False @@ -457,7 +453,7 @@ async def create_checkout_session( @router.get("/get-product/{product_id}") -async def get_product(product_id: str, crud: Annotated[Crud, Depends(Crud.get)]) -> Dict[str, Any]: +async def get_product(product_id: str, crud: Annotated[Crud, Depends(Crud.get)]) -> stripe.Product: try: # First get the listing by stripe_product_id listing = await crud.get_listing_by_stripe_product_id(product_id) @@ -472,13 +468,7 @@ async def get_product(product_id: str, crud: Annotated[Crud, Depends(Crud.get)]) # Retrieve the product using the seller's connected account product = stripe.Product.retrieve(product_id, stripe_account=seller.stripe_connect.account_id) - return { - "id": product.id, - "name": product.name, - "description": product.description, - "images": product.images, - "metadata": product.metadata, - } + return product except stripe.StripeError as e: logger.error(f"Stripe error retrieving product: {str(e)}") raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) @@ -535,7 +525,7 @@ async def create_connect_account( async def create_connect_account_session( user: Annotated[User, Depends(get_session_user_with_read_permission)], account_id: str = Body(..., embed=True), -) -> Dict[str, str]: +) -> dict[str, str]: try: logger.info("Creating account session for account: %s", account_id) @@ -565,11 +555,17 @@ async def create_connect_account_session( raise -@router.post("/connect/delete/accounts") +class DeleteTestAccountsResponse(BaseModel): + success: bool + deleted_accounts: list[str] + count: int + + +@router.post("/connect/delete/accounts", response_model=DeleteTestAccountsResponse) async def delete_test_accounts( user: Annotated[User, Depends(get_session_user_with_read_permission)], crud: Annotated[Crud, Depends(Crud.get)], -) -> Dict[str, Any]: +) -> DeleteTestAccountsResponse: if not user.permissions or "is_admin" not in user.permissions: raise HTTPException(status_code=403, detail="Admin permission required to delete accounts") @@ -584,7 +580,7 @@ async def delete_test_accounts( except Exception as e: logger.error("Failed to delete account %s: %s", account.id, str(e)) - return {"success": True, "deleted_accounts": deleted_accounts, "count": len(deleted_accounts)} + return DeleteTestAccountsResponse(success=True, deleted_accounts=deleted_accounts, count=len(deleted_accounts)) except Exception as e: logger.error("Error deleting test accounts: %s", str(e)) raise HTTPException(status_code=400, detail=str(e)) @@ -664,12 +660,17 @@ async def create_listing_product( ) -@router.post("/process-preorder/{order_id}") +class ProcessPreorderResponse(BaseModel): + status: str + checkout_session: stripe.checkout.Session | None + + +@router.post("/process-preorder/{order_id}", response_model=ProcessPreorderResponse) async def process_preorder( order_id: str, crud: Annotated[Crud, Depends(Crud.get)], user: User = Depends(get_session_user_with_admin_permission), -) -> Dict[str, Any]: +) -> ProcessPreorderResponse: async with crud: try: # Get the order and verify it's a preorder @@ -758,13 +759,10 @@ async def process_preorder( await crud.update_order(order_id, order_data) - return { - "status": "success", - "checkout_session": { - "id": checkout_session.id, - "url": checkout_session.url, - }, - } + return ProcessPreorderResponse( + status="success", + checkout_session=checkout_session, + ) except stripe.StripeError as e: logger.error(f"Stripe error processing preorder final payment: {str(e)}") diff --git a/store/app/utils/cloudfront_signer.py b/store/app/utils/cloudfront_signer.py index aba700d6..567f7922 100644 --- a/store/app/utils/cloudfront_signer.py +++ b/store/app/utils/cloudfront_signer.py @@ -5,7 +5,7 @@ import json from datetime import datetime, timedelta -from typing import Any, Dict, Optional +from typing import Any, Optional from botocore.signers import CloudFrontSigner from cryptography.hazmat.primitives import hashes, serialization @@ -66,7 +66,7 @@ def create_custom_policy(self, url: str, expire_days: float = 1, ip_range: Optio :return: The custom policy in JSON format. """ expiration_time = int((datetime.utcnow() + timedelta(days=expire_days)).timestamp()) - policy: Dict[str, Any] = { + policy: dict[str, Any] = { "Statement": [ { "Resource": url,