From 12671d285d3fbf7ee5fdda9f0256a7e67e90e621 Mon Sep 17 00:00:00 2001 From: Winston Hsiao <96440583+Winston-Hsiao@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:10:39 -0400 Subject: [PATCH] Clean tech debt (#521) * Update contributing with stripe instructions * Cleaned up route naming conventions and refined profil and admin UI * Robot listingID clickable on creation --- CONTRIBUTING.md | 21 +++- README.md | 7 +- .../components/modals/CancelOrderModal.tsx | 2 +- .../components/modals/EditAddressModal.tsx | 4 +- .../components/modals/RegisterRobotModal.tsx | 11 +- frontend/src/components/orders/OrderCard.tsx | 14 +-- frontend/src/components/pages/Orders.tsx | 4 +- frontend/src/components/pages/Profile.tsx | 100 ++++++++++-------- frontend/src/components/pages/StompyPro.tsx | 4 +- frontend/src/components/pages/Terminal.tsx | 31 +++++- frontend/src/components/ui/Input/Input.tsx | 9 +- frontend/src/gen/api.ts | 28 ++--- frontend/src/lib/types/api.ts | 1 + store/app/routers/orders.py | 10 +- store/app/routers/stripe.py | 2 +- store/app/routers/users.py | 9 ++ 16 files changed, 171 insertions(+), 86 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45ad95c6..4912fd3c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,6 +29,7 @@ You can contribute to the K-Scale Store project in various ways, such as reporti 4. [Syncing Frontend and Backend](#syncing-frontend-and-backend) 5. [React Setup](#react-setup) 6. [Testing](#testing) +7. [Stripe Setup](#stripe-setup) --- @@ -143,27 +144,33 @@ DYNAMO_ENDPOINT=http://127.0.0.1:4566 dynamodb-admin Create a Python virtual environment using [uv](https://astral.sh/blog/uv) or [virtualenv](https://virtualenv.pypa.io/en/latest/) with **Python 3.11 or later**: 1. **Using `uv`**: + ```bash uv venv .venv --python 3.11 ``` 2. **Using `virtualenv`**: If you choose to use `virtualenv`, ensure that **Python 3.11** is installed on your machine. You can check if it's installed by running: + ```bash python3.11 --version ``` + If Python 3.11 is installed, create the virtual environment with: + ```bash python3.11 -m venv .venv ``` + **Note**: If Python 3.11 is not installed on your machine, you will need to install it before proceeding. For macOS, you can install Python 3.11 using `Homebrew`: ```bash brew install python@3.11 ``` -### Activate the virtual environment: +### Activate the virtual environment: Once the virtual environment is created, activate it: + ```bash source .venv/bin/activate ``` @@ -249,6 +256,18 @@ make test-frontend # Run only the frontend tests make test-backend # Run only the backend tests ``` +## Stripe Setup + +Run this to recieve stripe webhooks locally: + +```bash +stripe listen --forward-to localhost:8080/stripe/webhook +``` + +Make sure to set the `STRIPE_WEBHOOK_SECRET` environment variable to the value +shown in the terminal and source it to the terminal you are running +`make start-backend` in. + ## Optional Install pre-commit from [here](https://pre-commit.com/) to run the formatting and static checks automatically when you commit. diff --git a/README.md b/README.md index d7da9c11..17b4b7ce 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,11 @@ # K-Scale Store -This is the code for our online store. This is a simple store for buying and selling robots and robot parts. +This is the codebase for our online store. + +This site contains: + +- a marketplace for buying and selling robots and robot parts +- a developer terminal for managing and interacting with your robots If you would like to contribute, see the [CONTRIBUTING.md](CONTRIBUTING.md) file and check out the [project tracker](https://github.com/orgs/kscalelabs/projects/8/views/1). diff --git a/frontend/src/components/modals/CancelOrderModal.tsx b/frontend/src/components/modals/CancelOrderModal.tsx index b8b793a2..726d9fac 100644 --- a/frontend/src/components/modals/CancelOrderModal.tsx +++ b/frontend/src/components/modals/CancelOrderModal.tsx @@ -13,7 +13,7 @@ import { useAlertQueue } from "@/hooks/useAlertQueue"; import { useAuthentication } from "@/hooks/useAuth"; type Order = - paths["/orders/get_user_orders"]["get"]["responses"][200]["content"]["application/json"][0]; + paths["/orders/user-orders"]["get"]["responses"][200]["content"]["application/json"][0]; interface CancelOrderModalProps { isOpen: boolean; diff --git a/frontend/src/components/modals/EditAddressModal.tsx b/frontend/src/components/modals/EditAddressModal.tsx index 83a6be60..8fd2c49d 100644 --- a/frontend/src/components/modals/EditAddressModal.tsx +++ b/frontend/src/components/modals/EditAddressModal.tsx @@ -14,7 +14,7 @@ import { useAlertQueue } from "@/hooks/useAlertQueue"; import { useAuthentication } from "@/hooks/useAuth"; type Order = - paths["/orders/get_user_orders"]["get"]["responses"][200]["content"]["application/json"][0]; + paths["/orders/user-orders"]["get"]["responses"][200]["content"]["application/json"][0]; interface EditAddressModalProps { isOpen: boolean; @@ -50,7 +50,7 @@ const EditAddressModal: React.FC = ({ e.preventDefault(); try { const { data, error } = await client.PUT( - "/orders/update_order_address/{order_id}", + "/orders/update-order-address/{order_id}", { params: { path: { order_id: order.id } }, body: address, diff --git a/frontend/src/components/modals/RegisterRobotModal.tsx b/frontend/src/components/modals/RegisterRobotModal.tsx index 0ac238c1..48fd92b1 100644 --- a/frontend/src/components/modals/RegisterRobotModal.tsx +++ b/frontend/src/components/modals/RegisterRobotModal.tsx @@ -11,7 +11,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { ApiError } from "@/lib/types/api"; -import { Plus } from "lucide-react"; +import { ExternalLink, Plus } from "lucide-react"; interface RegisterRobotModalProps { isOpen: boolean; @@ -145,7 +145,14 @@ export function RegisterRobotModal({ {error &&
{error}
} -
+
+
)} - {isAdmin && ( + {isAdmin && !canEdit && (
-
- - setFirstName(e.target.value)} - className="mt-1 block w-full" - /> -
+
+
+ + setFirstName(e.target.value)} + className="mt-1 block w-full" + /> +
-
- - setLastName(e.target.value)} - className="mt-1 block w-full" - /> +
+ + setLastName(e.target.value)} + className="mt-1 block w-full" + /> +
@@ -256,8 +268,8 @@ export const RenderProfile = (props: RenderProfileProps) => { id="bio" value={bio} onChange={(e) => setBio(e.target.value)} - className="mt-1 block w-full rounded-md border-gray-11 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" - rows={4} + className="mt-1 block w-full rounded-md border-gray-11 shadow-sm focus:border-primary-9 focus:ring focus:ring-primary-9 focus:ring-opacity-50" + rows={3} />
@@ -316,13 +328,13 @@ export const RenderProfile = (props: RenderProfileProps) => { Overview Upvoted diff --git a/frontend/src/components/pages/StompyPro.tsx b/frontend/src/components/pages/StompyPro.tsx index 017dc690..ab5afe4a 100644 --- a/frontend/src/components/pages/StompyPro.tsx +++ b/frontend/src/components/pages/StompyPro.tsx @@ -26,8 +26,8 @@ const StompyPro: React.FC = () => { "Regular software updates", ], price: 1600000, - // productId: "prod_Qyzd8f0gFMis7c", // developer - productId: "prod_R0n3nkCO4aQdlg", // production + productId: "prod_Qyzd8f0gFMis7c", // developer + // productId: "prod_R0n3nkCO4aQdlg", // production }; return ( diff --git a/frontend/src/components/pages/Terminal.tsx b/frontend/src/components/pages/Terminal.tsx index 2efe01e4..e3112d95 100644 --- a/frontend/src/components/pages/Terminal.tsx +++ b/frontend/src/components/pages/Terminal.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; import Spinner from "@/components/ui/Spinner"; import { Button } from "@/components/ui/button"; @@ -23,7 +22,6 @@ type ListingInfo = { }; const TerminalPage: React.FC = () => { - const navigate = useNavigate(); const { api, currentUser, isAuthenticated, isLoading } = useAuthentication(); const [robots, setRobots] = useState(null); const [loadingRobots, setLoadingRobots] = useState(true); @@ -110,6 +108,26 @@ const TerminalPage: React.FC = () => { throw error; } + // Fetch the listing info for the newly created robot + const { data: listingData, error: listingError } = await api.client.GET( + "/listings/batch", + { + params: { query: { ids: [robotData.listing_id] } }, + }, + ); + + if (!listingError && listingData.listings.length > 0) { + const listing = listingData.listings[0]; + setListingInfos((prev) => ({ + ...prev, + [listing.id]: { + id: listing.id, + username: listing.username || "", + slug: listing.slug, + }, + })); + } + setRobots((prev) => (prev ? [...prev, data] : [data])); setIsRegisterModalOpen(false); } catch (error) { @@ -259,8 +277,13 @@ const TerminalPage: React.FC = () => {

No robots yet. Link your robot to a listing to get started.

-
)} diff --git a/frontend/src/components/ui/Input/Input.tsx b/frontend/src/components/ui/Input/Input.tsx index fdcb953b..7ed5a26e 100644 --- a/frontend/src/components/ui/Input/Input.tsx +++ b/frontend/src/components/ui/Input/Input.tsx @@ -10,7 +10,14 @@ const Input = React.forwardRef< List[Order]: @@ -38,7 +38,7 @@ async def get_user_orders( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No orders found for this user") -@orders_router.get("/get_order/{order_id}", response_model=Order) +@orders_router.get("/order/{order_id}", response_model=Order) async def get_order( order_id: str, user: User = Depends(get_session_user_with_read_permission), crud: Crud = Depends(Crud.get) ) -> Order: @@ -48,7 +48,7 @@ async def get_order( return order -@orders_router.get("/get_order_with_product/{order_id}", response_model=OrderWithProduct) +@orders_router.get("/order-with-product/{order_id}", response_model=OrderWithProduct) async def get_order_with_product( order_id: str, user: User = Depends(get_session_user_with_read_permission), crud: Crud = Depends(Crud.get) ) -> OrderWithProduct: @@ -63,7 +63,7 @@ async def get_order_with_product( return OrderWithProduct(order=order, product=ProductInfo(**product)) -@orders_router.get("/get_user_orders_with_products", response_model=List[OrderWithProduct]) +@orders_router.get("/user-orders-with-products", response_model=List[OrderWithProduct]) async def get_user_orders_with_products( user: User = Depends(get_session_user_with_read_permission), crud: Crud = Depends(Crud.get) ) -> List[OrderWithProduct]: @@ -90,7 +90,7 @@ class UpdateOrderAddressRequest(BaseModel): shipping_country: str -@orders_router.put("/update_order_address/{order_id}", response_model=Order) +@orders_router.put("/update-order-address/{order_id}", response_model=Order) async def update_order_address( order_id: str, address_update: UpdateOrderAddressRequest, diff --git a/store/app/routers/stripe.py b/store/app/routers/stripe.py index 2bf33d56..3257f9ce 100644 --- a/store/app/routers/stripe.py +++ b/store/app/routers/stripe.py @@ -286,7 +286,7 @@ async def create_checkout_session( raise HTTPException(status_code=400, detail=str(e)) -@stripe_router.get("/get_product/{product_id}") +@stripe_router.get("/get-product/{product_id}") async def get_product(product_id: str) -> Dict[str, Any]: try: product = stripe.Product.retrieve(product_id) diff --git a/store/app/routers/users.py b/store/app/routers/users.py index 6bc49c87..fd050217 100644 --- a/store/app/routers/users.py +++ b/store/app/routers/users.py @@ -344,6 +344,15 @@ async def set_moderator( admin_user: Annotated[User, Depends(get_session_user_with_admin_permission)], crud: Annotated[Crud, Depends(Crud.get)], ) -> UserPublic: + # Get the target user first to check their permissions + target_user = await crud.get_user(request.user_id, throw_if_missing=True) + + # Prevent modifying admin users' moderator status + if target_user.permissions and "admin" in target_user.permissions: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="Cannot modify moderator status of admin users" + ) + updated_user = await crud.set_moderator(request.user_id, request.is_mod) return UserPublic(**updated_user.model_dump())