(FEAT): Migrated the home page APIS.
Just need a fix for the optinal authenticated user for the ROTW.
This commit is contained in:
parent
c0b76506c4
commit
38f3c87885
27
internal/app/server/authentication.go
Normal file
27
internal/app/server/authentication.go
Normal file
@ -0,0 +1,27 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
domain "github.com/haydenhargreaves/Potion/internal/domain/user"
|
||||
)
|
||||
|
||||
// AuthenticatedFunc is a function that handles authenticated requests
|
||||
type AuthenticatedFunc func(ctx *gin.Context, user *domain.User)
|
||||
|
||||
// withAuthenticatedUser is a helper to run a handler only if user is authenticated. Otherwise
|
||||
// the function will return an error with a 401 status.
|
||||
func (s *Server) withAuthenticatedUser(ctx *gin.Context, handler AuthenticatedFunc) {
|
||||
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
|
||||
}
|
||||
handler(ctx, user)
|
||||
}
|
||||
|
||||
// TODO: Create a function to use for methods that CAN use a user, but sometimes don't.
|
||||
@ -10,18 +10,18 @@ import (
|
||||
)
|
||||
|
||||
// JwtAuthMiddlewareV2 is responsible to protecting routes. Anything that may go wrong
|
||||
// will be returned via JSON with a 'message' field and a 401 error code. When this
|
||||
// will be returned via JSON with a 'message' field and a 401 error code. When this
|
||||
// middleware is successful, it will set the 'userId' and 'userEmail' fields and pass
|
||||
// to the next function in the chain.
|
||||
// to the next function in the chain.
|
||||
//
|
||||
// Functions that are called after this can assume that those values defined are always
|
||||
// set.
|
||||
func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
return func(ctx *gin.Context) {
|
||||
tokenString, err := ctx.Cookie("jwt_token")
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusUnauthorized, gin.H{
|
||||
"status": http.StatusUnauthorized,
|
||||
"status": http.StatusUnauthorized,
|
||||
"message": fmt.Sprintf("[UNAUTHORIZED] Failed to get token from cookie. %s", err.Error()),
|
||||
})
|
||||
ctx.Abort()
|
||||
@ -40,7 +40,7 @@ func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
|
||||
// Error occurred when parsing
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusUnauthorized, gin.H{
|
||||
"status": http.StatusUnauthorized,
|
||||
"status": http.StatusUnauthorized,
|
||||
"message": fmt.Sprintf("[UNAUTHORIZED] Error parsing cooking. %s", err.Error()),
|
||||
})
|
||||
ctx.Abort()
|
||||
@ -50,7 +50,7 @@ func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
|
||||
// Token is invalid
|
||||
if !token.Valid {
|
||||
ctx.JSON(http.StatusUnauthorized, gin.H{
|
||||
"status": http.StatusUnauthorized,
|
||||
"status": http.StatusUnauthorized,
|
||||
"message": "[UNAUTHORIZED] Token is invalid.",
|
||||
})
|
||||
ctx.Abort()
|
||||
|
||||
@ -211,6 +211,9 @@ func (s *Server) Setup() *Server {
|
||||
router_api_v2.GET("/user/favorites", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserFavoritesV2)
|
||||
router_api_v2.GET("/user/engagement", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserEngagementV2)
|
||||
|
||||
router_api_v2.GET("/user/recipes/made", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserMadeRecipesV2)
|
||||
router_api_v2.GET("/user/recipes/viewed", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserViewedRecipesV2)
|
||||
|
||||
router_api_v2.GET("/protected", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), func(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusOK, gin.H{"msg": "YAY"})
|
||||
})
|
||||
|
||||
@ -5,99 +5,110 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
domain "github.com/haydenhargreaves/Potion/internal/domain/user"
|
||||
)
|
||||
|
||||
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,
|
||||
"message": "[UNAUTHORIZED] Could not fetch authenticated user.",
|
||||
s.withAuthenticatedUser(ctx, func(ctx *gin.Context, user *domain.User) {
|
||||
ctx.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusOK,
|
||||
"message": "[OK] Successfully retrieved authenticated user.",
|
||||
"user": user,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
s.withAuthenticatedUser(ctx, func(ctx *gin.Context, user *domain.User) {
|
||||
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
|
||||
}
|
||||
|
||||
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()),
|
||||
ctx.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusOK,
|
||||
"message": "[OK] Successfully retrieved authenticated user's recipes.",
|
||||
"recipes": recipes,
|
||||
})
|
||||
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
|
||||
}
|
||||
s.withAuthenticatedUser(ctx, func(ctx *gin.Context, user *domain.User) {
|
||||
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
|
||||
}
|
||||
|
||||
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()),
|
||||
ctx.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusOK,
|
||||
"message": "[OK] Successfully retrieved authenticated user's favorites.",
|
||||
"favorites": favorites,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusOK,
|
||||
"message": "[OK] Successfully retrieved authenticated user's favorites.",
|
||||
"favorites": favorites,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) GetAuthenicatedUserEngagementV2(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
|
||||
}
|
||||
s.withAuthenticatedUser(ctx, func(ctx *gin.Context, user *domain.User) {
|
||||
engagement, err := s.deps.EngagementService.GetUserEngagement(user.Id, 6)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||
"status": http.StatusBadRequest,
|
||||
"message": fmt.Sprintf("[FAILED] Failed to get authenticated user engagement. %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
engagement, err := s.deps.EngagementService.GetUserEngagement(user.Id, 6)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||
"status": http.StatusBadRequest,
|
||||
"message": fmt.Sprintf("[FAILED] Failed to get authenticated user engagement. %s", err.Error()),
|
||||
ctx.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusOK,
|
||||
"message": "[OK] Successfully retrieved authenticated user engagement.",
|
||||
"engagement": engagement,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) GetAuthenicatedUserMadeRecipesV2(ctx *gin.Context) {
|
||||
s.withAuthenticatedUser(ctx, func(ctx *gin.Context, user *domain.User) {
|
||||
recipes, err := s.deps.RecipeService.GetUserMadeRecipes(user.Id, 6)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||
"status": http.StatusBadRequest,
|
||||
"message": fmt.Sprintf("[FAILED] Failed to get authenticated user's made recipes. %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusOK,
|
||||
"message": "[OK] Successfully retrieved authenticated user's made recipes.",
|
||||
"recipes": recipes,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) GetAuthenicatedUserViewedRecipesV2(ctx *gin.Context) {
|
||||
s.withAuthenticatedUser(ctx, func(ctx *gin.Context, user *domain.User) {
|
||||
recipes, err := s.deps.RecipeService.GetUserViewedRecipes(user.Id, 6)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||
"status": http.StatusBadRequest,
|
||||
"message": fmt.Sprintf("[FAILED] Failed to get authenticated user's viewed recipes. %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusOK,
|
||||
"message": "[OK] Successfully retrieved authenticated user's viewed recipes.",
|
||||
"recipes": recipes,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusOK,
|
||||
"message": "[OK] Successfully retrieved authenticated user engagement.",
|
||||
"engagement": engagement,
|
||||
})
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import RecipeSearchBar from "../components/inputs/RecipeSearchBar";
|
||||
import { GetRecipeOfTheWeek } from "../services/RecipeService";
|
||||
import { isApiError, type ApiError } from "../types/api/error";
|
||||
import { AuthContext } from "../context/AuthContext";
|
||||
import { GetAuthenticatedUserMadeRecipes, GetAuthenticateUserViewedRecipes } from "../services/UserService";
|
||||
|
||||
export default function Home() {
|
||||
// Context
|
||||
@ -24,104 +25,33 @@ export default function Home() {
|
||||
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
|
||||
// BUG: Remove these
|
||||
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
|
||||
};
|
||||
|
||||
setRecipeOfTheWeek(recipe);
|
||||
|
||||
const recipes: Recipe[] = [recipe, recipe2];
|
||||
setMadeRecipes(recipes);
|
||||
setViewedRecipes(recipes);
|
||||
}, []);
|
||||
|
||||
// TODO: Fetch other items when needed
|
||||
// Fetch the recipe of the week
|
||||
useEffect(() => {
|
||||
async function fetch() {
|
||||
const result: Recipe | ApiError = await GetRecipeOfTheWeek();
|
||||
if (isApiError(result)) {
|
||||
setError(result.message);
|
||||
return;
|
||||
const result_rotw: Recipe | ApiError = await GetRecipeOfTheWeek();
|
||||
if (isApiError(result_rotw)) {
|
||||
setError(result_rotw.message);
|
||||
} else {
|
||||
setRecipeOfTheWeek(result_rotw);
|
||||
}
|
||||
setRecipeOfTheWeek(result);
|
||||
|
||||
if (isLoggedIn) {
|
||||
const result_made: Recipe[] | ApiError = await GetAuthenticatedUserMadeRecipes();
|
||||
if (isApiError(result_made)) {
|
||||
setError(result_made.message);
|
||||
} else {
|
||||
setMadeRecipes(result_made);
|
||||
}
|
||||
|
||||
const result_viewed: Recipe[] | ApiError = await GetAuthenticateUserViewedRecipes();
|
||||
if (isApiError(result_viewed)) {
|
||||
setError(result_viewed.message);
|
||||
} else {
|
||||
setViewedRecipes(result_viewed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
void fetch();
|
||||
}, []);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import axios from "axios";
|
||||
import type { ApiError } from "../types/api/error";
|
||||
import type { User } from "../types/user";
|
||||
import type { GetAuthenticateUserEngagementResponse, GetAuthenticateUserFavoritesResponse, GetAuthenticateUserRecipesResponse, GetAuthenticateUserResponse } from "../types/api/user";
|
||||
import type { GetAuthenticateUserEngagementResponse, GetAuthenticateUserFavoritesResponse, GetAuthenticateUserMadeRecipesResponse, GetAuthenticateUserRecipesResponse, GetAuthenticateUserResponse, GetAuthenticateUserViewedRecipesResponse } from "../types/api/user";
|
||||
import type { Recipe } from "../types/recipe";
|
||||
import type { Engagement } from "../types/engagement";
|
||||
|
||||
@ -61,3 +61,31 @@ export async function GetAuthenticatedUserEngagement(): Promise<Engagement[] | A
|
||||
|
||||
return response.data.engagement;
|
||||
}
|
||||
|
||||
export async function GetAuthenticatedUserMadeRecipes(): Promise<Recipe[] | ApiError> {
|
||||
const response = await axios.get<GetAuthenticateUserMadeRecipesResponse>("http://localhost:3000/v2/api/user/recipes/made");
|
||||
|
||||
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 GetAuthenticateUserViewedRecipes(): Promise<Recipe[] | ApiError> {
|
||||
const response = await axios.get<GetAuthenticateUserViewedRecipesResponse>("http://localhost:3000/v2/api/user/recipes/viewed");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -25,3 +25,15 @@ export interface GetAuthenticateUserEngagementResponse {
|
||||
message: string;
|
||||
engagement?: Engagement[];
|
||||
}
|
||||
|
||||
export interface GetAuthenticateUserMadeRecipesResponse {
|
||||
status: number;
|
||||
message: string;
|
||||
recipes?: Recipe[];
|
||||
}
|
||||
|
||||
export interface GetAuthenticateUserViewedRecipesResponse {
|
||||
status: number;
|
||||
message: string;
|
||||
recipes?: Recipe[];
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user