Compare commits
No commits in common. "38f3c8788543bf3828c77a44a61e4b6078bb8047" and "f66a9900403c52ce34fe29ac67c05fa86a6fd5b7" have entirely different histories.
38f3c87885
...
f66a990040
@ -1,27 +0,0 @@
|
|||||||
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,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// JwtAuthMiddlewareV2 is responsible to protecting routes. Anything that may go wrong
|
// 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
|
// 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
|
// Functions that are called after this can assume that those values defined are always
|
||||||
// set.
|
// set.
|
||||||
func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
|
func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
|
||||||
return func(ctx *gin.Context) {
|
return func(ctx *gin.Context) {
|
||||||
tokenString, err := ctx.Cookie("jwt_token")
|
tokenString, err := ctx.Cookie("jwt_token")
|
||||||
|
fmt.Println(tokenString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusUnauthorized, gin.H{
|
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()),
|
"message": fmt.Sprintf("[UNAUTHORIZED] Failed to get token from cookie. %s", err.Error()),
|
||||||
})
|
})
|
||||||
ctx.Abort()
|
ctx.Abort()
|
||||||
@ -40,7 +41,7 @@ func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
|
|||||||
// Error occurred when parsing
|
// Error occurred when parsing
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusUnauthorized, gin.H{
|
ctx.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"status": http.StatusUnauthorized,
|
"status": http.StatusUnauthorized,
|
||||||
"message": fmt.Sprintf("[UNAUTHORIZED] Error parsing cooking. %s", err.Error()),
|
"message": fmt.Sprintf("[UNAUTHORIZED] Error parsing cooking. %s", err.Error()),
|
||||||
})
|
})
|
||||||
ctx.Abort()
|
ctx.Abort()
|
||||||
@ -50,7 +51,7 @@ func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
|
|||||||
// Token is invalid
|
// Token is invalid
|
||||||
if !token.Valid {
|
if !token.Valid {
|
||||||
ctx.JSON(http.StatusUnauthorized, gin.H{
|
ctx.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"status": http.StatusUnauthorized,
|
"status": http.StatusUnauthorized,
|
||||||
"message": "[UNAUTHORIZED] Token is invalid.",
|
"message": "[UNAUTHORIZED] Token is invalid.",
|
||||||
})
|
})
|
||||||
ctx.Abort()
|
ctx.Abort()
|
||||||
|
|||||||
@ -209,10 +209,6 @@ func (s *Server) Setup() *Server {
|
|||||||
router_api_v2.GET("/user", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenticatedUserHandlerV2)
|
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/recipes", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserRecipesV2)
|
||||||
router_api_v2.GET("/user/favorites", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserFavoritesV2)
|
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) {
|
router_api_v2.GET("/protected", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), func(ctx *gin.Context) {
|
||||||
ctx.JSON(http.StatusOK, gin.H{"msg": "YAY"})
|
ctx.JSON(http.StatusOK, gin.H{"msg": "YAY"})
|
||||||
|
|||||||
@ -5,110 +5,73 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/user"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) GetAuthenticatedUserHandlerV2(ctx *gin.Context) {
|
func (s *Server) GetAuthenticatedUserHandlerV2(ctx *gin.Context) {
|
||||||
s.withAuthenticatedUser(ctx, func(ctx *gin.Context, user *domain.User) {
|
user := s.deps.UserService.GetAuthenicatedUser(ctx)
|
||||||
ctx.JSON(http.StatusOK, gin.H{
|
if user == nil {
|
||||||
"status": http.StatusOK,
|
ctx.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"message": "[OK] Successfully retrieved authenticated user.",
|
"status": http.StatusUnauthorized,
|
||||||
"user": user,
|
"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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetAuthenicatedUserRecipesV2(ctx *gin.Context) {
|
func (s *Server) GetAuthenicatedUserRecipesV2(ctx *gin.Context) {
|
||||||
s.withAuthenticatedUser(ctx, func(ctx *gin.Context, user *domain.User) {
|
user := s.deps.UserService.GetAuthenicatedUser(ctx)
|
||||||
recipes, err := s.deps.RecipeService.GetUserRecipes(user.Id)
|
if user == nil {
|
||||||
if err != nil {
|
ctx.JSON(http.StatusUnauthorized, gin.H{
|
||||||
ctx.JSON(http.StatusBadRequest, gin.H{
|
"status": http.StatusUnauthorized,
|
||||||
"status": http.StatusBadRequest,
|
"message": "[UNAUTHORIZED] Could not fetch authenticated user.",
|
||||||
"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,
|
|
||||||
})
|
})
|
||||||
|
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) {
|
func (s *Server) GetAuthenicatedUserFavoritesV2(ctx *gin.Context) {
|
||||||
s.withAuthenticatedUser(ctx, func(ctx *gin.Context, user *domain.User) {
|
user := s.deps.UserService.GetAuthenicatedUser(ctx)
|
||||||
favorites, err := s.deps.RecipeService.GetUserFavoriteRecipes(user.Id)
|
if user == nil {
|
||||||
if err != nil {
|
ctx.JSON(http.StatusUnauthorized, gin.H{
|
||||||
ctx.JSON(http.StatusBadRequest, gin.H{
|
"status": http.StatusUnauthorized,
|
||||||
"status": http.StatusBadRequest,
|
"message": "[UNAUTHORIZED] Could not fetch authenticated user.",
|
||||||
"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,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) GetAuthenicatedUserEngagementV2(ctx *gin.Context) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
34
shell.nix
34
shell.nix
@ -1,34 +0,0 @@
|
|||||||
{ pkgs ? import <nixpkgs> {} }:
|
|
||||||
|
|
||||||
pkgs.mkShell {
|
|
||||||
buildInputs = with pkgs; [
|
|
||||||
go
|
|
||||||
gopls
|
|
||||||
go-tools
|
|
||||||
htmx-lsp2
|
|
||||||
templ
|
|
||||||
tailwindcss_4
|
|
||||||
tailwindcss-language-server
|
|
||||||
watchman
|
|
||||||
docker-language-server
|
|
||||||
dockerfile-language-server-nodejs
|
|
||||||
gcc_multi
|
|
||||||
glibc_multi
|
|
||||||
nodejs
|
|
||||||
];
|
|
||||||
|
|
||||||
shellHook = ''
|
|
||||||
alias vim="nvim"
|
|
||||||
alias vi="nvim"
|
|
||||||
alias v="nvim"
|
|
||||||
|
|
||||||
# Modify this
|
|
||||||
export PS1="\[\e[35m\]\w \$ \[\e[0m\]"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "The default environment is ready!"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
exec zsh
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
@ -5,14 +5,6 @@ interface ActivityListItemProps {
|
|||||||
engagement: Engagement;
|
engagement: Engagement;
|
||||||
}
|
}
|
||||||
|
|
||||||
function FormatDate(date: Date): string {
|
|
||||||
return new Intl.DateTimeFormat("en-US", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "2-digit",
|
|
||||||
day: "2-digit"
|
|
||||||
}).format(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ActivityListItem({ engagement }: ActivityListItemProps) {
|
export default function ActivityListItem({ engagement }: ActivityListItemProps) {
|
||||||
return <>
|
return <>
|
||||||
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 duration-150 flex justify-between items-center">
|
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 duration-150 flex justify-between items-center">
|
||||||
@ -20,7 +12,7 @@ export default function ActivityListItem({ engagement }: ActivityListItemProps)
|
|||||||
{engagement.Message}
|
{engagement.Message}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs md:text-sm text-gray-600 w-fit shrink-0">
|
<p className="text-xs md:text-sm text-gray-600 w-fit shrink-0">
|
||||||
{FormatDate(new Date(engagement.Created))}
|
{engagement.Created.toLocaleDateString()}
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
</>;
|
</>;
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import RecipeSearchBar from "../components/inputs/RecipeSearchBar";
|
|||||||
import { GetRecipeOfTheWeek } from "../services/RecipeService";
|
import { GetRecipeOfTheWeek } from "../services/RecipeService";
|
||||||
import { isApiError, type ApiError } from "../types/api/error";
|
import { isApiError, type ApiError } from "../types/api/error";
|
||||||
import { AuthContext } from "../context/AuthContext";
|
import { AuthContext } from "../context/AuthContext";
|
||||||
import { GetAuthenticatedUserMadeRecipes, GetAuthenticateUserViewedRecipes } from "../services/UserService";
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
// Context
|
// Context
|
||||||
@ -25,33 +24,104 @@ export default function Home() {
|
|||||||
|
|
||||||
const [error, setError] = useState<string>("");
|
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
|
// TODO: Fetch other items when needed
|
||||||
// Fetch the recipe of the week
|
// Fetch the recipe of the week
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
const result_rotw: Recipe | ApiError = await GetRecipeOfTheWeek();
|
const result: Recipe | ApiError = await GetRecipeOfTheWeek();
|
||||||
if (isApiError(result_rotw)) {
|
if (isApiError(result)) {
|
||||||
setError(result_rotw.message);
|
setError(result.message);
|
||||||
} else {
|
return;
|
||||||
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();
|
void fetch();
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import RecipeListItem from "../components/results/RecipeListItem";
|
|||||||
import type { Engagement } from "../types/engagement";
|
import type { Engagement } from "../types/engagement";
|
||||||
import ActivityListItem from "../components/results/ActivityListItem";
|
import ActivityListItem from "../components/results/ActivityListItem";
|
||||||
import { AuthContext } from "../context/AuthContext";
|
import { AuthContext } from "../context/AuthContext";
|
||||||
import { GetAuthenticatedUser, GetAuthenticatedUserEngagement, GetAuthenticatedUserFavorites, GetAuthenticatedUserRecipes } from "../services/UserService";
|
import { GetAuthenticatedUser, GetAuthenticatedUserFavorites, GetAuthenticatedUserRecipes } from "../services/UserService";
|
||||||
import { isApiError, type ApiError } from "../types/api/error";
|
import { isApiError, type ApiError } from "../types/api/error";
|
||||||
import { Logout } from "../services/AuthService";
|
import { Logout } from "../services/AuthService";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@ -23,16 +23,26 @@ export default function Profile() {
|
|||||||
const [activity, setActivity] = useState<Engagement[]>([]);
|
const [activity, setActivity] = useState<Engagement[]>([]);
|
||||||
const [jwt, setJwt] = useState<string>("");
|
const [jwt, setJwt] = useState<string>("");
|
||||||
|
|
||||||
|
// BUG: Remove this, used for testing
|
||||||
|
useEffect(() => {
|
||||||
|
const eng: Engagement = {
|
||||||
|
Id: 1,
|
||||||
|
Type: "made",
|
||||||
|
Message: "Created some shit",
|
||||||
|
Entity: 1,
|
||||||
|
UserId: 1,
|
||||||
|
Created: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
setActivity([eng]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Log the user out and direct to the home page
|
// Log the user out and direct to the home page
|
||||||
const logoutHandler = (): void => {
|
const logoutHandler = (): void => {
|
||||||
void Logout();
|
void Logout();
|
||||||
void navigate("/v2/web/home");
|
void navigate("/v2/web/home");
|
||||||
}
|
}
|
||||||
|
|
||||||
const seeAllRecipesHandler = (): void => void navigate("/v2/web/404");
|
|
||||||
const seeAllFavoritesHandler = (): void => void navigate("/v2/web/favorites");
|
|
||||||
const seeAllEngagementHandler = (): void => void navigate("/v2/web/404");
|
|
||||||
|
|
||||||
const fetchProfileData = async (): Promise<void> => {
|
const fetchProfileData = async (): Promise<void> => {
|
||||||
const result_user: User | ApiError = await GetAuthenticatedUser();
|
const result_user: User | ApiError = await GetAuthenticatedUser();
|
||||||
if (isApiError(result_user)) {
|
if (isApiError(result_user)) {
|
||||||
@ -54,13 +64,6 @@ export default function Profile() {
|
|||||||
} else {
|
} else {
|
||||||
setFavorites(result_favorites);
|
setFavorites(result_favorites);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result_engagement: Engagement[] | ApiError = await GetAuthenticatedUserEngagement();
|
|
||||||
if (isApiError(result_engagement)) {
|
|
||||||
setError(result_engagement.message);
|
|
||||||
} else {
|
|
||||||
setActivity(result_engagement);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the JWT from the cookies
|
// Get the JWT from the cookies
|
||||||
@ -80,13 +83,6 @@ export default function Profile() {
|
|||||||
console.log("@error", error);
|
console.log("@error", error);
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (activity)
|
|
||||||
console.log("@activity", activity);
|
|
||||||
}, [activity]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* User Details Section */}
|
{/* User Details Section */}
|
||||||
@ -125,11 +121,11 @@ export default function Profile() {
|
|||||||
) : (
|
) : (
|
||||||
recipes.slice(0, 4).map(recipe => <RecipeListItem key={recipe.Id} recipe={recipe} />)
|
recipes.slice(0, 4).map(recipe => <RecipeListItem key={recipe.Id} recipe={recipe} />)
|
||||||
)}
|
)}
|
||||||
<button onClick={seeAllRecipesHandler} className="w-full">
|
<a href="">
|
||||||
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 hover:text-blue-600 duration-150 text-center">
|
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 hover:text-blue-600 duration-150 text-center">
|
||||||
See all...
|
See all...
|
||||||
</li>
|
</li>
|
||||||
</button>
|
</a>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -142,11 +138,11 @@ export default function Profile() {
|
|||||||
) : (
|
) : (
|
||||||
favorites.slice(0, 4).map(recipe => <RecipeListItem key={recipe.Id} recipe={recipe} />)
|
favorites.slice(0, 4).map(recipe => <RecipeListItem key={recipe.Id} recipe={recipe} />)
|
||||||
)}
|
)}
|
||||||
<button onClick={seeAllFavoritesHandler} className="w-full">
|
<a href="">
|
||||||
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 hover:text-blue-600 duration-150 text-center">
|
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 hover:text-blue-600 duration-150 text-center">
|
||||||
See all...
|
See all...
|
||||||
</li>
|
</li>
|
||||||
</button>
|
</a>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -155,11 +151,11 @@ export default function Profile() {
|
|||||||
<h2 className="text-2xl font-semibold text-gray-800">Recent Activity</h2>
|
<h2 className="text-2xl font-semibold text-gray-800">Recent Activity</h2>
|
||||||
<ul className="w-full my-2">
|
<ul className="w-full my-2">
|
||||||
{activity?.map(act => <ActivityListItem key={act.Id} engagement={act} />)}
|
{activity?.map(act => <ActivityListItem key={act.Id} engagement={act} />)}
|
||||||
<button onClick={seeAllEngagementHandler} className="w-full">
|
<a href="">
|
||||||
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 hover:text-blue-600 duration-150 text-center">
|
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 hover:text-blue-600 duration-150 text-center">
|
||||||
See all...
|
See all...
|
||||||
</li>
|
</li>
|
||||||
</button>
|
</a>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import type { ApiError } from "../types/api/error";
|
import type { ApiError } from "../types/api/error";
|
||||||
import type { User } from "../types/user";
|
import type { User } from "../types/user";
|
||||||
import type { GetAuthenticateUserEngagementResponse, GetAuthenticateUserFavoritesResponse, GetAuthenticateUserMadeRecipesResponse, GetAuthenticateUserRecipesResponse, GetAuthenticateUserResponse, GetAuthenticateUserViewedRecipesResponse } from "../types/api/user";
|
import type { GetAuthenticateUserFavoritesResponse, GetAuthenticateUserRecipesResponse, GetAuthenticateUserResponse } from "../types/api/user";
|
||||||
import type { Recipe } from "../types/recipe";
|
import type { Recipe } from "../types/recipe";
|
||||||
import type { Engagement } from "../types/engagement";
|
|
||||||
|
|
||||||
|
|
||||||
export async function GetAuthenticatedUser(): Promise<User | ApiError> {
|
export async function GetAuthenticatedUser(): Promise<User | ApiError> {
|
||||||
@ -47,45 +46,3 @@ export async function GetAuthenticatedUserFavorites(): Promise<Recipe[] | ApiErr
|
|||||||
|
|
||||||
return response.data.favorites;
|
return response.data.favorites;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function GetAuthenticatedUserEngagement(): Promise<Engagement[] | ApiError> {
|
|
||||||
const response = await axios.get<GetAuthenticateUserEngagementResponse>("http://localhost:3000/v2/api/user/engagement");
|
|
||||||
|
|
||||||
if (response.data.status !== 200 || response.data.engagement === undefined) {
|
|
||||||
const err: ApiError = {
|
|
||||||
status: response.data.status,
|
|
||||||
message: response.data.message
|
|
||||||
};
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import type { Engagement } from "../engagement";
|
|
||||||
import type { Recipe } from "../recipe";
|
import type { Recipe } from "../recipe";
|
||||||
import type { User } from "../user";
|
import type { User } from "../user";
|
||||||
|
|
||||||
@ -19,21 +18,3 @@ export interface GetAuthenticateUserFavoritesResponse {
|
|||||||
message: string;
|
message: string;
|
||||||
favorites?: Recipe[];
|
favorites?: Recipe[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetAuthenticateUserEngagementResponse {
|
|
||||||
status: number;
|
|
||||||
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