From 00acb981b0561ef4b1e1a9010f27b101b9a5c756 Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Thu, 20 Nov 2025 12:40:49 -0700 Subject: [PATCH] (FEAT): Created GetUserV2 API. This is used to finish the recipe display page! --- internal/app/server/server.go | 1 + internal/app/server/user_handler_v2.go | 28 +++++++++++++++++++ web/src/components/buttons/FavoriteButton.tsx | 1 - web/src/pages/Recipe.tsx | 19 ++++++++++++- web/src/services/UserService.ts | 15 +++++++++- web/src/types/api/user.ts | 6 ++++ 6 files changed, 67 insertions(+), 3 deletions(-) diff --git a/internal/app/server/server.go b/internal/app/server/server.go index 798b16d..b141f6e 100644 --- a/internal/app/server/server.go +++ b/internal/app/server/server.go @@ -209,6 +209,7 @@ func (s *Server) Setup() *Server { router_api_v2.GET("/auth/logout", s.LogoutHandlerV2) router_api_v2.GET("/user", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenticatedUserHandlerV2) + router_api_v2.GET("/user/:id", s.GetUserV2) router_api_v2.GET("/user/recipes", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserRecipesV2) router_api_v2.GET("/user/favorites", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserFavoritesV2) router_api_v2.GET("/user/engagement", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserEngagementV2) diff --git a/internal/app/server/user_handler_v2.go b/internal/app/server/user_handler_v2.go index 9de4a9c..120e04a 100644 --- a/internal/app/server/user_handler_v2.go +++ b/internal/app/server/user_handler_v2.go @@ -3,11 +3,39 @@ package server import ( "fmt" "net/http" + "strconv" "github.com/gin-gonic/gin" domain "github.com/haydenhargreaves/Potion/internal/domain/user" ) +func (s *Server) GetUserV2(ctx *gin.Context) { + id := ctx.Param("id") + parsedId, err := strconv.Atoi(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "status": http.StatusBadRequest, + "message": fmt.Sprintf("[ERROR] Failed to parse ID parameter. %s", err.Error()), + }) + return + } + + user, err := s.deps.UserService.GetUser(parsedId) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "status": http.StatusBadRequest, + "message": fmt.Sprintf("[ERROR] Failed to get the target user. %s", err.Error()), + }) + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "status": http.StatusOK, + "message": "[OK] Successfully retrieved target user.", + "user": user, + }) +} + func (s *Server) GetAuthenticatedUserHandlerV2(ctx *gin.Context) { s.withAuthenticatedUser(ctx, func(ctx *gin.Context, user *domain.User) { ctx.JSON(http.StatusOK, gin.H{ diff --git a/web/src/components/buttons/FavoriteButton.tsx b/web/src/components/buttons/FavoriteButton.tsx index cc1d39e..d7a71be 100644 --- a/web/src/components/buttons/FavoriteButton.tsx +++ b/web/src/components/buttons/FavoriteButton.tsx @@ -13,7 +13,6 @@ interface FavoriteButtonProps { export default function FavoriteButton({ favorite, id }: FavoriteButtonProps) { // CONTEXT const { isLoggedIn } = use(AuthContext); - const navigate = useNavigate(); const [_favorite, setFavorite] = useState(); diff --git a/web/src/pages/Recipe.tsx b/web/src/pages/Recipe.tsx index 9471183..0c3bc12 100644 --- a/web/src/pages/Recipe.tsx +++ b/web/src/pages/Recipe.tsx @@ -13,6 +13,8 @@ import TagList from "../components/items/TagList"; import IngredientList from "../components/items/IngredientList"; import InstructionList from "../components/items/InstructionList"; import Spinner from "../components/Spinner"; +import { GetUser } from "../services/UserService"; +import type { User } from "../types/user"; export default function RecipePage() { // Url params @@ -20,6 +22,7 @@ export default function RecipePage() { // Page state const [recipe, setRecipe] = useState(null); + const [author, setAuthor] = useState(null); const [error, setError] = useState(""); useEffect(() => { @@ -34,6 +37,20 @@ export default function RecipePage() { void fetch(); }, [id]); + useEffect(() => { + async function fetch() { + if (!recipe) return; + + const result: User | ApiError = await GetUser(recipe.UserId); + if (isApiError(result)) { + setError(result.message); + } else { + setAuthor(result); + } + } + void fetch(); + }, [recipe]); + // BUG: Prob remove useEffect(() => { if (error) @@ -45,7 +62,7 @@ export default function RecipePage() {

{recipe.Title}

-

Author: loading...

+

{author ? author.Name : "Loading..."}

Category: {recipe.Category}

diff --git a/web/src/services/UserService.ts b/web/src/services/UserService.ts index fc58d4a..cd785a3 100644 --- a/web/src/services/UserService.ts +++ b/web/src/services/UserService.ts @@ -1,10 +1,23 @@ import axios from "axios"; import type { ApiError } from "../types/api/error"; import type { User } from "../types/user"; -import type { GetAuthenticateUserEngagementResponse, GetAuthenticateUserFavoritesResponse, GetAuthenticateUserMadeRecipesResponse, GetAuthenticateUserRecipesResponse, GetAuthenticateUserResponse, GetAuthenticateUserViewedRecipesResponse } from "../types/api/user"; +import type { GetAuthenticateUserEngagementResponse, GetAuthenticateUserFavoritesResponse, GetAuthenticateUserMadeRecipesResponse, GetAuthenticateUserRecipesResponse, GetAuthenticateUserResponse, GetAuthenticateUserViewedRecipesResponse, GetUserResponse } from "../types/api/user"; import type { Recipe } from "../types/recipe"; import type { Engagement } from "../types/engagement"; +export async function GetUser(id: number): Promise { + const response = await axios.get(`http://localhost:3000/v2/api/user/${id}`); + + if (response.data.status !== 200 || response.data.user === undefined) { + const err: ApiError = { + status: response.data.status, + message: response.data.message + }; + return err; + } + + return response.data.user; +} export async function GetAuthenticatedUser(): Promise { const response = await axios.get("http://localhost:3000/v2/api/user"); diff --git a/web/src/types/api/user.ts b/web/src/types/api/user.ts index 5f784cd..8ac1f9f 100644 --- a/web/src/types/api/user.ts +++ b/web/src/types/api/user.ts @@ -2,6 +2,12 @@ import type { Engagement } from "../engagement"; import type { Recipe } from "../recipe"; import type { User } from "../user"; +export interface GetUserResponse { + status: number; + message: string; + user?: User; +} + export interface GetAuthenticateUserResponse { status: number; message: string;