From d70fc374d905f2684ba8040603deaedaad716779 Mon Sep 17 00:00:00 2001 From: Jake Kim Date: Thu, 25 Sep 2025 10:30:51 -0700 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20browse=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EC=97=90=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/browse/[userId].tsx | 79 ++++++++++++++++++++++++++++++++--- src/server/api/routers/s3.ts | 12 +++--- src/utils/api.ts | 5 +++ 3 files changed, 85 insertions(+), 11 deletions(-) diff --git a/src/pages/browse/[userId].tsx b/src/pages/browse/[userId].tsx index fa2e2f8..34a4b8a 100644 --- a/src/pages/browse/[userId].tsx +++ b/src/pages/browse/[userId].tsx @@ -1,11 +1,9 @@ -import { - GetServerSideProps, +import type { GetStaticPaths, GetStaticProps, - InferGetServerSidePropsType, InferGetStaticPropsType, } from 'next'; -import { createServerSideHelpers } from '@trpc/react-query/server'; +import { useState, useEffect, useCallback } from 'react'; import { api } from '~/utils/api'; import { formatDistanceToNow } from 'date-fns'; import styled from '@emotion/styled'; @@ -45,11 +43,80 @@ const FileCard = styled('div')({ const BrowsePage = ({ userId, }: InferGetStaticPropsType) => { + const [token, setToken] = useState(null); + const utils = api.useContext(); + + const { data: currentUser } = api.account.getCurrentUser.useQuery(undefined, { + enabled: !!token, + }); + + const signInMutation = api.account.signIn.useMutation({ + onSuccess: (data) => { + setToken(data.token); + localStorage.setItem('authToken', data.token); + void utils.s3.listUserFiles.invalidate(); + }, + }); + const { data: filesData } = api.s3.listUserFiles.useQuery( - { userId }, - { enabled: userId !== undefined } + undefined, + { enabled: !!token } ); + const handleLogin = useCallback(async () => { + const email = window.prompt('Email:'); + if (!email) return; + + const password = window.prompt('Password:'); + if (!password) return; + + void signInMutation.mutate({ email, password }); + }, [signInMutation]); + + // Check for stored token on mount + useEffect(() => { + const storedToken = localStorage.getItem('authToken'); + if (storedToken) { + if (!currentUser || currentUser.name === userId) { + setToken(storedToken); + } else { + localStorage.removeItem('authToken'); + } + } else if (!signInMutation.isPending && !signInMutation.error) { + void handleLogin(); + } + }, [currentUser, userId, handleLogin, signInMutation]); + + if (signInMutation.error) { + return ( + + Login Failed +
+ Please refresh to try again. +
+
+ ); + } + + if (!token || !currentUser) { + return ( + + Waiting for authentication... + + ); + } + + if (currentUser.name !== userId) { + return ( + + Access Denied +
+ You cannot access files that do not belong to you. +
+
+ ); + } + return ( My Files diff --git a/src/server/api/routers/s3.ts b/src/server/api/routers/s3.ts index 3eb8f73..d7428f2 100644 --- a/src/server/api/routers/s3.ts +++ b/src/server/api/routers/s3.ts @@ -6,10 +6,9 @@ import { env } from '~/env.js'; import { createTRPCRouter, protectedProcedure, - publicProcedure, } from '~/server/api/trpc'; export const s3Router = createTRPCRouter({ - listUserFiles: publicProcedure + listUserFiles: protectedProcedure .meta({ openapi: { method: 'GET', @@ -19,7 +18,7 @@ export const s3Router = createTRPCRouter({ description: 'Returns list of files uploaded by the current user', }, }) - .input(z.object({ userId: z.string() })) + .input(z.void()) .output( z.object({ files: z.array( @@ -33,10 +32,13 @@ export const s3Router = createTRPCRouter({ ), }) ) - .query(async ({ ctx, input: { userId } }) => { + .query(async ({ ctx }) => { + // Use the authenticated user's name from the session + const userName = ctx.session.user.name; + const response = await ctx.s3.listObjectsV2({ Bucket: env.BUCKET_NAME, - Prefix: `${userId}/`, // Only list objects that are in the user's folder + Prefix: `${userName}/`, // Only list objects that are in the user's folder }); return { diff --git a/src/utils/api.ts b/src/utils/api.ts index 0f03d30..7e40ebc 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -40,6 +40,11 @@ export const api = createTRPCNext({ */ transformer: superjson, url: `${getBaseUrl()}/api/trpc`, + headers() { + // Add auth token to all requests + const token = localStorage.getItem("authToken"); + return token ? { Authorization: `Bearer ${token}` } : {}; + }, }), ], }; From 0ea6c77dac32d173a7b1d7dffec797333b8ec3c4 Mon Sep 17 00:00:00 2001 From: Jake Kim Date: Fri, 26 Sep 2025 00:19:26 -0700 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20path=EC=97=90=EC=84=9C=20userId=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=ED=95=98=EC=97=AC=20browse=20=EB=8B=A8?= =?UTF-8?q?=EC=88=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/browse/{[userId].tsx => index.tsx} | 50 ++++++-------------- 1 file changed, 15 insertions(+), 35 deletions(-) rename src/pages/browse/{[userId].tsx => index.tsx} (76%) diff --git a/src/pages/browse/[userId].tsx b/src/pages/browse/index.tsx similarity index 76% rename from src/pages/browse/[userId].tsx rename to src/pages/browse/index.tsx index 34a4b8a..0682512 100644 --- a/src/pages/browse/[userId].tsx +++ b/src/pages/browse/index.tsx @@ -1,7 +1,6 @@ import type { - GetStaticPaths, - GetStaticProps, - InferGetStaticPropsType, + GetServerSideProps, + InferGetServerSidePropsType } from 'next'; import { useState, useEffect, useCallback } from 'react'; import { api } from '~/utils/api'; @@ -41,8 +40,8 @@ const FileCard = styled('div')({ }); const BrowsePage = ({ - userId, -}: InferGetStaticPropsType) => { + login, +}: InferGetServerSidePropsType) => { const [token, setToken] = useState(null); const utils = api.useContext(); @@ -76,23 +75,19 @@ const BrowsePage = ({ // Check for stored token on mount useEffect(() => { const storedToken = localStorage.getItem('authToken'); - if (storedToken) { - if (!currentUser || currentUser.name === userId) { - setToken(storedToken); - } else { - localStorage.removeItem('authToken'); - } - } else if (!signInMutation.isPending && !signInMutation.error) { + if (storedToken && !login) { + setToken(storedToken); + } else if (!signInMutation.isSuccess && !signInMutation.isPending && !signInMutation.error) { void handleLogin(); } - }, [currentUser, userId, handleLogin, signInMutation]); + }, [currentUser, login, handleLogin, signInMutation]); if (signInMutation.error) { return ( - Login Failed + Access Denied
- Please refresh to try again. + Login failed. Please refresh to try again.
); @@ -102,16 +97,8 @@ const BrowsePage = ({ return ( Waiting for authentication... - - ); - } - - if (currentUser.name !== userId) { - return ( - - Access Denied
- You cannot access files that do not belong to you. + Please refresh this page if nothing happens.
); @@ -146,21 +133,14 @@ const BrowsePage = ({ ); }; -export const getStaticProps = (async ({ params }) => { - const userId = params!.userId as string; +export const getServerSideProps = (async ({ query }) => { + const login = query?.login ?? null; return { props: { - userId, + login, }, }; -}) satisfies GetStaticProps; - -export const getStaticPaths = (async () => { - return { - paths: [], - fallback: 'blocking', - }; -}) satisfies GetStaticPaths; +}) satisfies GetServerSideProps; export default BrowsePage;