Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 2 additions & 14 deletions apps/server/src/modules/account/repositories/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import { db, eq, sql } from "@fixr/db/connection";
import { clients, companies, employees, users } from "@fixr/db/schema";
import { accountSchema } from "@fixr/schemas/account";
<<<<<<< HEAD
import { Cached } from "../../../shared/infra/cache";
=======
import { redis } from "../../../config/redis";
import {
accountCacheKey,
CACHE_TTL,
jwtPayloadCacheKey,
} from "../../../core/lib/cache";
>>>>>>> 990eba6 (Feat(Server): Add avatar upload presign, update, and remove endpoints)
import { Cached, InvalidateCache } from "../../../shared/infra/cache";

/** @description Account data access layer */
export class AccountRepository {
Expand Down Expand Up @@ -62,11 +53,8 @@ export class AccountRepository {
* @param userId - The user ID
* @param avatarUrl - The new avatar URL
*/
@InvalidateCache({ patterns: ["account:*", "jwt:*"] })
static async updateAvatarUrl(userId: string, avatarUrl: string | null) {
await db.update(users).set({ avatarUrl }).where(eq(users.id, userId));

const cacheKey = accountCacheKey(userId);
const jwtCacheKey = jwtPayloadCacheKey(userId);
await Promise.all([redis.del(cacheKey), redis.del(jwtCacheKey)]);
}
}
5 changes: 5 additions & 0 deletions apps/web/app/(protected)/dashboard/[subdomain]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SessionProvider } from "@/lib/hooks/use-session";
import QueryClientWrapper from "@/lib/query-client";
import "../../../globals.css";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { Header } from "@/components/dashboard/sidebar/header";
import { ThemeProvider } from "@/components/theme-provider";
import { ThemedToaster } from "@/components/themed-toaster";
Expand Down Expand Up @@ -35,6 +36,10 @@ export default async function RootLayout({
const cookieStore = await cookies();
const session = getSession(cookieStore);

if (!session) {
redirect("/auth/login");
}

return (
<html lang="en" suppressHydrationWarning>
<body
Expand Down
18 changes: 10 additions & 8 deletions apps/web/lib/auth/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ export async function signOut(cookieString?: string) {
: await axios.get("/auth/signout");
}

export function getSession(cookies?: ReadonlyRequestCookies) {
if (cookies) {
const jwt = parseJwt(cookies.get(cookieKey("session"))?.value);
return userJWT.parse(jwt);
}
export function getSession(
cookies?: ReadonlyRequestCookies
): ReturnType<typeof userJWT.parse> | null {
const jwt = cookies
? parseJwt(cookies.get(cookieKey("session"))?.value)
: parseJwt(parseCookies()[cookieKey("session")]);

const cookieStore = parseCookies();
const jwt = parseJwt(cookieStore[cookieKey("session")]); // Corrected access using cookieKey
return userJWT.parse(jwt);
// Missing/expired sessions are expected (the middleware revalidates the token
// before this runs); fail gracefully to null instead of throwing a ZodError.
const result = userJWT.safeParse(jwt);
return result.success ? result.data : null;
}

export const getClientSession = (): ReturnType<typeof userJWT.parse> | null => {
Expand Down
18 changes: 17 additions & 1 deletion apps/web/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,23 @@ async function revalidate(request: NextRequest, isProtectedRoute: boolean) {
if (revalidateResponse.ok) {
const setCookies = revalidateResponse.headers.getSetCookie();
if (setCookies.length > 0) {
const res = NextResponse.next();
/**
* Apply the refreshed cookies to the current request as well, so Server
* Components rendered right after this middleware (e.g. getSession) see
* the new session instead of the expired one that triggered the revalidation.
*/
for (const cookie of setCookies) {
const nameValue = cookie.split(";")[0] ?? "";
const separatorIndex = nameValue.indexOf("=");
if (separatorIndex === -1) {
continue;
}
const name = nameValue.slice(0, separatorIndex).trim();
const value = nameValue.slice(separatorIndex + 1).trim();
request.cookies.set(name, value);
}

const res = NextResponse.next({ request });
for (const cookie of setCookies) {
res.headers.append("Set-Cookie", cookie);
}
Expand Down