(FEAT): Profile APIs are almost totally migrated!

This commit is contained in:
Hayden Hargreaves 2025-11-14 23:46:46 -07:00
parent 983d326a47
commit f66a990040
5 changed files with 133 additions and 103 deletions

View File

@ -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"})

View File

@ -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,
})
}

View File

@ -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<void> => {
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 != "" ? (
<img
className="w-24 md:w-32 border-2 border-blue-500 rounded-full shadow-blue-500 shadow select-none"
src={user?.ImageUrl ?? ""}
src={user?.ImageUrl ?? undefined}
/>
) : (
<img

View File

@ -1,13 +1,14 @@
import axios from "axios";
import type { ApiError } from "../types/api/error";
import type { User } from "../types/user";
import type { GetAuthenticateUserResponse } from "../types/api/user";
import type { GetAuthenticateUserFavoritesResponse, GetAuthenticateUserRecipesResponse, GetAuthenticateUserResponse } from "../types/api/user";
import type { Recipe } from "../types/recipe";
export async function GetAuthenticatedUser(): Promise<User | ApiError> {
const response = await axios.get<GetAuthenticateUserResponse>("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<User | ApiError> {
return response.data.user;
}
export async function GetAuthenticatedUserRecipes(): Promise<Recipe[] | ApiError> {
const response = await axios.get<GetAuthenticateUserRecipesResponse>("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<Recipe[] | ApiError> {
const response = await axios.get<GetAuthenticateUserFavoritesResponse>("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;
}

View File

@ -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[];
}