From c0b76506c4ada8234ba06e712a4c53499bd47e3d Mon Sep 17 00:00:00 2001
From: Hayden Hargreaves
Date: Sat, 15 Nov 2025 23:26:16 -0700
Subject: [PATCH] (FEAT): Profile page APIs are complete!!!!
This also includes a shell.nix file for use just in case the flake
isn't.
---
internal/app/server/middleware_v2.go | 1 -
internal/app/server/server.go | 1 +
internal/app/server/user_handler_v2.go | 26 +++++++++++
shell.nix | 34 ++++++++++++++
.../components/results/ActivityListItem.tsx | 10 +++-
web/src/pages/Profile.tsx | 46 ++++++++++---------
web/src/services/UserService.ts | 17 ++++++-
web/src/types/api/user.ts | 7 +++
8 files changed, 118 insertions(+), 24 deletions(-)
create mode 100644 shell.nix
diff --git a/internal/app/server/middleware_v2.go b/internal/app/server/middleware_v2.go
index 4d4ade4..c661adb 100644
--- a/internal/app/server/middleware_v2.go
+++ b/internal/app/server/middleware_v2.go
@@ -19,7 +19,6 @@ import (
func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
return func(ctx *gin.Context) {
tokenString, err := ctx.Cookie("jwt_token")
- fmt.Println(tokenString)
if err != nil {
ctx.JSON(http.StatusUnauthorized, gin.H{
"status": http.StatusUnauthorized,
diff --git a/internal/app/server/server.go b/internal/app/server/server.go
index ee90cd5..bf96b00 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("/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("/user/engagement", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserEngagementV2)
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 048dc6a..4143ee1 100644
--- a/internal/app/server/user_handler_v2.go
+++ b/internal/app/server/user_handler_v2.go
@@ -75,3 +75,29 @@ func (s *Server) GetAuthenicatedUserFavoritesV2(ctx *gin.Context) {
"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
+ }
+
+ 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,
+ })
+}
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000..b51a1fa
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,34 @@
+{ pkgs ? import {} }:
+
+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
+ '';
+}
diff --git a/web/src/components/results/ActivityListItem.tsx b/web/src/components/results/ActivityListItem.tsx
index de262ba..813edc8 100644
--- a/web/src/components/results/ActivityListItem.tsx
+++ b/web/src/components/results/ActivityListItem.tsx
@@ -5,6 +5,14 @@ interface ActivityListItemProps {
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) {
return <>
@@ -12,7 +20,7 @@ export default function ActivityListItem({ engagement }: ActivityListItemProps)
{engagement.Message}
- {engagement.Created.toLocaleDateString()}
+ {FormatDate(new Date(engagement.Created))}
>;
diff --git a/web/src/pages/Profile.tsx b/web/src/pages/Profile.tsx
index e47d5e3..e88652b 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, GetAuthenticatedUserFavorites, GetAuthenticatedUserRecipes } from "../services/UserService";
+import { GetAuthenticatedUser, GetAuthenticatedUserEngagement, GetAuthenticatedUserFavorites, GetAuthenticatedUserRecipes } from "../services/UserService";
import { isApiError, type ApiError } from "../types/api/error";
import { Logout } from "../services/AuthService";
import { useNavigate } from "react-router-dom";
@@ -23,26 +23,16 @@ export default function Profile() {
const [activity, setActivity] = useState([]);
const [jwt, setJwt] = useState("");
- // 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
const logoutHandler = (): void => {
void Logout();
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 => {
const result_user: User | ApiError = await GetAuthenticatedUser();
if (isApiError(result_user)) {
@@ -64,6 +54,13 @@ export default function Profile() {
} else {
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
@@ -83,6 +80,13 @@ export default function Profile() {
console.log("@error", error);
}, [error]);
+ useEffect(() => {
+ if (activity)
+ console.log("@activity", activity);
+ }, [activity]);
+
+
+
return (
<>
{/* User Details Section */}
@@ -121,11 +125,11 @@ export default function Profile() {
) : (
recipes.slice(0, 4).map(recipe => )
)}
-
+
+
@@ -138,11 +142,11 @@ export default function Profile() {
) : (
favorites.slice(0, 4).map(recipe => )
)}
-
+
+
@@ -151,11 +155,11 @@ export default function Profile() {
Recent Activity
diff --git a/web/src/services/UserService.ts b/web/src/services/UserService.ts
index b1a1650..a24e682 100644
--- a/web/src/services/UserService.ts
+++ b/web/src/services/UserService.ts
@@ -1,8 +1,9 @@
import axios from "axios";
import type { ApiError } from "../types/api/error";
import type { User } from "../types/user";
-import type { GetAuthenticateUserFavoritesResponse, GetAuthenticateUserRecipesResponse, GetAuthenticateUserResponse } from "../types/api/user";
+import type { GetAuthenticateUserEngagementResponse, GetAuthenticateUserFavoritesResponse, GetAuthenticateUserRecipesResponse, GetAuthenticateUserResponse } from "../types/api/user";
import type { Recipe } from "../types/recipe";
+import type { Engagement } from "../types/engagement";
export async function GetAuthenticatedUser(): Promise {
@@ -46,3 +47,17 @@ export async function GetAuthenticatedUserFavorites(): Promise {
+ const response = await axios.get("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;
+}
diff --git a/web/src/types/api/user.ts b/web/src/types/api/user.ts
index c69dafc..0a0db86 100644
--- a/web/src/types/api/user.ts
+++ b/web/src/types/api/user.ts
@@ -1,3 +1,4 @@
+import type { Engagement } from "../engagement";
import type { Recipe } from "../recipe";
import type { User } from "../user";
@@ -18,3 +19,9 @@ export interface GetAuthenticateUserFavoritesResponse {
message: string;
favorites?: Recipe[];
}
+
+export interface GetAuthenticateUserEngagementResponse {
+ status: number;
+ message: string;
+ engagement?: Engagement[];
+}