diff --git a/internal/app/server/server.go b/internal/app/server/server.go index 54dd6ff..ee90cd5 100644 --- a/internal/app/server/server.go +++ b/internal/app/server/server.go @@ -207,6 +207,8 @@ 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/recipes", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserRecipesV2) + router_api_v2.GET("/user/favorites", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserFavoritesV2) router_api_v2.GET("/protected", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), func(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{"msg": "YAY"}) diff --git a/internal/app/server/user_handler_v2.go b/internal/app/server/user_handler_v2.go index e5ff8a4..048dc6a 100644 --- a/internal/app/server/user_handler_v2.go +++ b/internal/app/server/user_handler_v2.go @@ -1,25 +1,77 @@ package server import ( + "fmt" "net/http" "github.com/gin-gonic/gin" ) - func (s *Server) GetAuthenticatedUserHandlerV2(ctx *gin.Context) { user := s.deps.UserService.GetAuthenicatedUser(ctx) if user == nil { ctx.JSON(http.StatusUnauthorized, gin.H{ - "status": http.StatusUnauthorized, + "status": http.StatusUnauthorized, "message": "[UNAUTHORIZED] Could not fetch authenticated user.", }) return } - ctx.JSON(http.StatusOK, gin.H{ - "status": http.StatusOK, - "message": "[OK] Successfully retrieved authenticated user.", - "user": user, - }) + ctx.JSON(http.StatusOK, gin.H{ + "status": http.StatusOK, + "message": "[OK] Successfully retrieved authenticated user.", + "user": user, + }) +} + +func (s *Server) GetAuthenicatedUserRecipesV2(ctx *gin.Context) { + user := s.deps.UserService.GetAuthenicatedUser(ctx) + if user == nil { + ctx.JSON(http.StatusUnauthorized, gin.H{ + "status": http.StatusUnauthorized, + "message": "[UNAUTHORIZED] Could not fetch authenticated user.", + }) + return + } + + recipes, err := s.deps.RecipeService.GetUserRecipes(user.Id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "status": http.StatusBadRequest, + "message": fmt.Sprintf("[FAILED] Could not fetch authenticated users's recipes. %s", err.Error()), + }) + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "status": http.StatusOK, + "message": "[OK] Successfully retrieved authenticated user's recipes.", + "recipes": recipes, + }) +} + +func (s *Server) GetAuthenicatedUserFavoritesV2(ctx *gin.Context) { + user := s.deps.UserService.GetAuthenicatedUser(ctx) + if user == nil { + ctx.JSON(http.StatusUnauthorized, gin.H{ + "status": http.StatusUnauthorized, + "message": "[UNAUTHORIZED] Could not fetch authenticated user.", + }) + return + } + + favorites, err := s.deps.RecipeService.GetUserFavoriteRecipes(user.Id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "status": http.StatusBadRequest, + "message": fmt.Sprintf("[FAILED] Could not fetch authenticated users's favorites. %s", err.Error()), + }) + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "status": http.StatusOK, + "message": "[OK] Successfully retrieved authenticated user's favorites.", + "favorites": favorites, + }) } diff --git a/web/src/pages/Profile.tsx b/web/src/pages/Profile.tsx index c773ba1..e47d5e3 100644 --- a/web/src/pages/Profile.tsx +++ b/web/src/pages/Profile.tsx @@ -5,7 +5,7 @@ import RecipeListItem from "../components/results/RecipeListItem"; import type { Engagement } from "../types/engagement"; import ActivityListItem from "../components/results/ActivityListItem"; import { AuthContext } from "../context/AuthContext"; -import { GetAuthenticatedUser } from "../services/UserService"; +import { GetAuthenticatedUser, GetAuthenticatedUserFavorites, GetAuthenticatedUserRecipes } from "../services/UserService"; import { isApiError, type ApiError } from "../types/api/error"; import { Logout } from "../services/AuthService"; import { useNavigate } from "react-router-dom"; @@ -25,84 +25,6 @@ export default function Profile() { // BUG: Remove this, used for testing useEffect(() => { - const recipe: Recipe = { - Id: 1, - Title: "Classic Pancakes", - Description: "Fluffy and delicious pancakes perfect for breakfast.", - Instructions: [ - "In a bowl, mix all the dry ingredients.", - "In another bowl, whisk the wet ingredients.", - "Combine both mixes until smooth.", - "Heat a non-stick skillet and pour batter.", - "Cook until bubbles form, flip and cook the other side.", - "Serve warm with syrup." - ], - Serves: 4, - Difficulty: 2, // scale 1-5 (example) - Duration: { - Total: 20, - Prep: 5, - Cook: 15 - }, - Category: "breakfast", - Ingredients: [ - { Name: "Flour", Quantity: "2 cups" }, - { Name: "Milk", Quantity: "1.5 cups" }, - { Name: "Egg", Quantity: "1 large" }, - { Name: "Baking Powder", Quantity: "2 teaspoons" }, - { Name: "Salt", Quantity: "0.5 teaspoon" }, - { Name: "Sugar", Quantity: "1 tablespoon" } - ], - UserId: 101, - Modified: new Date("2025-10-30T09:00:00"), - Created: new Date("2025-10-01T08:30:00"), - Tags: [ - { Id: 1, Name: "easy", Created: new Date("2025-01-01T12:00:00") }, - { Id: 2, Name: "quick", Created: new Date("2025-01-02T12:00:00") }, - { Id: 3, Name: "breakfast", Created: new Date("2025-01-03T12:00:00") } - ], - Favorite: true - }; - - const recipe2: Recipe = { - Id: 2, - Title: "Classic Pancakes", - Description: "Fluffy and delicious pancakes perfect for breakfast.", - Instructions: [ - "In a bowl, mix all the dry ingredients.", - "In another bowl, whisk the wet ingredients.", - "Combine both mixes until smooth.", - "Heat a non-stick skillet and pour batter.", - "Cook until bubbles form, flip and cook the other side.", - "Serve warm with syrup." - ], - Serves: 4, - Difficulty: 2, // scale 1-5 (example) - Duration: { - Total: 20, - Prep: 5, - Cook: 15 - }, - Category: "breakfast", - Ingredients: [ - { Name: "Flour", Quantity: "2 cups" }, - { Name: "Milk", Quantity: "1.5 cups" }, - { Name: "Egg", Quantity: "1 large" }, - { Name: "Baking Powder", Quantity: "2 teaspoons" }, - { Name: "Salt", Quantity: "0.5 teaspoon" }, - { Name: "Sugar", Quantity: "1 tablespoon" } - ], - UserId: 101, - Modified: new Date("2025-10-30T09:00:00"), - Created: new Date("2025-10-01T08:30:00"), - Tags: [ - { Id: 1, Name: "easy", Created: new Date("2025-01-01T12:00:00") }, - { Id: 2, Name: "quick", Created: new Date("2025-01-02T12:00:00") }, - { Id: 3, Name: "breakfast", Created: new Date("2025-01-03T12:00:00") } - ], - Favorite: true - }; - const eng: Engagement = { Id: 1, Type: "made", @@ -112,8 +34,6 @@ export default function Profile() { Created: new Date(), }; - setRecipes([recipe, recipe2]); - setFavorites([recipe, recipe2]); setActivity([eng]); }, []); @@ -123,6 +43,29 @@ export default function Profile() { void navigate("/v2/web/home"); } + const fetchProfileData = async (): Promise => { + const result_user: User | ApiError = await GetAuthenticatedUser(); + if (isApiError(result_user)) { + setError(result_user.message); + } else { + setUser(result_user); + } + + const result_recipes: Recipe[] | ApiError = await GetAuthenticatedUserRecipes(); + if (isApiError(result_recipes)) { + setError(result_recipes.message); + } else { + setRecipes(result_recipes); + } + + const result_favorites: Recipe[] | ApiError = await GetAuthenticatedUserFavorites(); + if (isApiError(result_favorites)) { + setError(result_favorites.message); + } else { + setFavorites(result_favorites); + } + } + // Get the JWT from the cookies useEffect(() => { setJwt(getJwt()); @@ -130,18 +73,9 @@ export default function Profile() { // Get the user when the JWTS change useEffect(() => { - // No jwt, we can't get a user - if (!jwt) return; - - async function fetch() { - const result: User | ApiError = await GetAuthenticatedUser(); - if (isApiError(result)) { - setError(result.message); - return; - } - setUser(result); - } - void fetch(); + // No jwt, we can't get user data + if (jwt) + void fetchProfileData(); }, [jwt]); useEffect(() => { @@ -157,7 +91,7 @@ export default function Profile() { {user?.ImageUrl != "" ? ( ) : ( { const response = await axios.get("http://localhost:3000/v2/api/user"); - if (response.data.status !== 200 || response.data.user === undefined){ + if (response.data.status !== 200 || response.data.user === undefined) { const err: ApiError = { status: response.data.status, message: response.data.message @@ -17,3 +18,31 @@ export async function GetAuthenticatedUser(): Promise { return response.data.user; } + +export async function GetAuthenticatedUserRecipes(): Promise { + const response = await axios.get("http://localhost:3000/v2/api/user/recipes"); + + if (response.data.status !== 200 || response.data.recipes === undefined) { + const err: ApiError = { + status: response.data.status, + message: response.data.message + }; + return err; + } + + return response.data.recipes; +} + +export async function GetAuthenticatedUserFavorites(): Promise { + const response = await axios.get("http://localhost:3000/v2/api/user/favorites"); + + if (response.data.status !== 200 || response.data.favorites === undefined) { + const err: ApiError = { + status: response.data.status, + message: response.data.message + }; + return err; + } + + return response.data.favorites; +} diff --git a/web/src/types/api/user.ts b/web/src/types/api/user.ts index 8720013..c69dafc 100644 --- a/web/src/types/api/user.ts +++ b/web/src/types/api/user.ts @@ -1,3 +1,4 @@ +import type { Recipe } from "../recipe"; import type { User } from "../user"; export interface GetAuthenticateUserResponse { @@ -5,3 +6,15 @@ export interface GetAuthenticateUserResponse { message: string; user?: User; } + +export interface GetAuthenticateUserRecipesResponse { + status: number; + message: string; + recipes?: Recipe[]; +} + +export interface GetAuthenticateUserFavoritesResponse { + status: number; + message: string; + favorites?: Recipe[]; +}