feat: implement complex selection flow

This commit is contained in:
Jose Selesan
2026-04-20 15:47:47 -03:00
parent a5e39c94c5
commit 28fd51f926
17 changed files with 453 additions and 38 deletions

View File

@@ -2,7 +2,9 @@ import { requireAuth } from '@/middlewares/require-auth.middleware';
import { createComplexHandler } from '@/modules/complex/handlers/create-complex.handler';
import { getComplexByIdHandler } from '@/modules/complex/handlers/get-complex-by-id.handler';
import { getComplexBySlugHandler } from '@/modules/complex/handlers/get-complex-by-slug.handler';
import { getCurrentComplexHandler } from '@/modules/complex/handlers/get-current-complex.handler';
import { listMyComplexesHandler } from '@/modules/complex/handlers/list-my-complexes.handler';
import { selectComplexHandler } from '@/modules/complex/handlers/select-complex.handler';
import { updateComplexHandler } from '@/modules/complex/handlers/update-complex.handler';
import type { AppEnv } from '@/types/hono';
import { zValidator } from '@hono/zod-validator';
@@ -17,6 +19,8 @@ const complexSlugParamsSchema = z.object({ slug: z.string().trim().min(1) });
complexRoutes.use('*', requireAuth);
complexRoutes.post('/', zValidator('json', createComplexSchema), createComplexHandler);
complexRoutes.get('/me', getCurrentComplexHandler);
complexRoutes.post('/select', selectComplexHandler);
complexRoutes.get('/mine', listMyComplexesHandler);
complexRoutes.get(
'/slug/:slug',

View File

@@ -0,0 +1,22 @@
import { getCurrentComplex } from '@/modules/complex/services/complex.service';
import type { AppContext } from '@/types/hono';
import { getCookie } from 'hono/cookie';
const SELECTED_COMPLEX_COOKIE = 'selected-complex-id';
export async function getCurrentComplexHandler(c: AppContext) {
const user = c.get('user');
const complexId = getCookie(c, SELECTED_COMPLEX_COOKIE);
if (!complexId) {
return c.json(null);
}
const complex = await getCurrentComplex(user.id, complexId);
if (!complex) {
return c.json(null);
}
return c.json(complex);
}

View File

@@ -0,0 +1,38 @@
import { selectComplex } from '@/modules/complex/services/complex.service';
import type { AppContext } from '@/types/hono';
import { setCookie } from 'hono/cookie';
import { z } from 'zod';
const SELECTED_COMPLEX_COOKIE = 'selected-complex-id';
const COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
const selectComplexSchema = z.object({
complexId: z.string().uuid(),
});
export async function selectComplexHandler(c: AppContext) {
const user = c.get('user');
const body = await c.req.json();
const result = selectComplexSchema.safeParse(body);
if (!result.success) {
return c.json({ message: 'Invalid payload', issues: result.error.issues }, { status: 400 });
}
const { complexId } = result.data;
const complex = await selectComplex(user.id, complexId);
if (!complex) {
return c.json({ message: 'Complex not found or not associated with user' }, { status: 404 });
}
setCookie(c, SELECTED_COMPLEX_COOKIE, complexId, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
path: '/',
maxAge: COOKIE_MAX_AGE,
});
return c.json(complex);
}

View File

@@ -104,7 +104,52 @@ export async function listMyComplexes(userId: string) {
},
});
return complexUsers.map((complexUser) => complexUser.complex);
return complexUsers.map((complexUser) => ({
...complexUser.complex,
role: complexUser.role,
}));
}
export async function getCurrentComplex(userId: string, complexId: string) {
const complexUser = await db.complexUser.findUnique({
where: {
complexId_userId: {
complexId,
userId,
},
},
include: {
complex: true,
},
});
if (!complexUser) return null;
return {
...complexUser.complex,
role: complexUser.role,
};
}
export async function selectComplex(userId: string, complexId: string) {
const complexUser = await db.complexUser.findUnique({
where: {
complexId_userId: {
complexId,
userId,
},
},
include: {
complex: true,
},
});
if (!complexUser) return null;
return {
...complexUser.complex,
role: complexUser.role,
};
}
export async function updateComplex(id: string, input: UpdateComplexInput) {

View File

@@ -1,8 +1,12 @@
import { getUserProfile } from '@/modules/user/services/user.service';
import type { AppContext } from '@/types/hono';
import { getCookie } from 'hono/cookie';
export function getUserProfileHandler(c: AppContext) {
const SELECTED_COMPLEX_COOKIE = 'selected-complex-id';
export async function getUserProfileHandler(c: AppContext) {
const user = c.get('user');
const profile = getUserProfile(user);
const complexId = getCookie(c, SELECTED_COMPLEX_COOKIE);
const profile = await getUserProfile(user, complexId);
return c.json(profile);
}

View File

@@ -1,5 +1,6 @@
import { createHash } from 'node:crypto';
import type { User } from '@/lib/auth';
import { db } from '@/lib/prisma';
import type { UserProfile } from '@repo/api-contract';
function getGravatarUrl(email: string): string {
@@ -7,12 +8,29 @@ function getGravatarUrl(email: string): string {
return `https://www.gravatar.com/avatar/${hash}?d=identicon`;
}
export function getUserProfile(user: User): UserProfile {
export async function getUserProfile(user: User, complexId?: string): Promise<UserProfile> {
let role = 'member';
if (complexId) {
const complexUser = await db.complexUser.findUnique({
where: {
complexId_userId: {
complexId,
userId: user.id,
},
},
});
if (complexUser) {
role = complexUser.role.toLowerCase();
}
}
return {
id: user.id,
fullName: user.name,
email: user.email,
role: (user as any).role || 'member',
role,
avatarUrl: user.image || getGravatarUrl(user.email),
createdAt: user.createdAt.toISOString(),
};