base con autenticacion, registro, modulo encuestas
This commit is contained in:
120
apps/web/lib/auth.config.ts
Normal file
120
apps/web/lib/auth.config.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { SignInAction } from '@/feactures/auth/actions/login-action';
|
||||
import { resfreshTokenAction } from '@/feactures/auth/actions/refresh-token-action';
|
||||
import { CredentialsSignin, NextAuthConfig, Session, User } from 'next-auth';
|
||||
import { DefaultJWT } from 'next-auth/jwt';
|
||||
import CredentialProvider from 'next-auth/providers/credentials';
|
||||
|
||||
const authConfig: NextAuthConfig = {
|
||||
providers: [
|
||||
CredentialProvider({
|
||||
credentials: {
|
||||
username: {
|
||||
type: 'username',
|
||||
},
|
||||
password: {
|
||||
type: 'password',
|
||||
},
|
||||
},
|
||||
async authorize(
|
||||
credentials: Partial<Record<'username' | 'password', unknown>>,
|
||||
request: Request,
|
||||
): Promise<User | null> {
|
||||
const credential = {
|
||||
username: credentials?.username as string,
|
||||
password: credentials?.password as string,
|
||||
};
|
||||
|
||||
const response = await SignInAction(credential);
|
||||
|
||||
if (
|
||||
response &&
|
||||
'type' in response &&
|
||||
(response.type === 'API_ERROR' ||
|
||||
response.type === 'VALIDATION_ERROR')
|
||||
) {
|
||||
throw new CredentialsSignin(response.message);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
id: response?.user.id?.toString() ?? '0',
|
||||
username: response?.user.username ?? '',
|
||||
fullname: response?.user.fullname ?? '',
|
||||
email: response?.user.email ?? '',
|
||||
role: response?.user.rol ?? [], // Add role array
|
||||
access_token: response?.tokens.access_token ?? '',
|
||||
access_expire_in: response?.tokens.access_expire_in ?? 0,
|
||||
refresh_token: response?.tokens.refresh_token ?? '',
|
||||
refresh_expire_in: response?.tokens.refresh_expire_in ?? 0,
|
||||
};
|
||||
|
||||
|
||||
},
|
||||
}),
|
||||
],
|
||||
pages: {
|
||||
signIn: '/', //sigin page
|
||||
},
|
||||
callbacks: {
|
||||
async jwt({
|
||||
token,
|
||||
user,
|
||||
account,
|
||||
}: {
|
||||
token: any;
|
||||
user: User;
|
||||
account: any;
|
||||
}) {
|
||||
// Si es un nuevo login, asignamos los datos
|
||||
if (user) {
|
||||
token.id = user.id;
|
||||
token.username = user.username;
|
||||
token.fullname = user.fullname;
|
||||
token.email = user.email;
|
||||
token.role = user.role;
|
||||
token.access_token = user.access_token;
|
||||
token.access_expire_in = user.access_expire_in;
|
||||
token.refresh_token = user.refresh_token;
|
||||
token.refresh_expire_in = user.refresh_expire_in;
|
||||
}
|
||||
|
||||
// Renovar access_token si ha expirado
|
||||
if (Date.now() / 1000 > (token.access_expire_in as number)) {
|
||||
if (Date.now() / 1000 > (token.refresh_expire_in as number)) {
|
||||
return null; // Forzar logout
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await resfreshTokenAction({
|
||||
token: token.refresh_token as string,
|
||||
});
|
||||
if (!res) throw new Error('Failed to refresh token');
|
||||
token.access_token = res.tokens.access_token;
|
||||
token.access_expire_in = res.tokens.access_expire_in;
|
||||
token.refresh_token = res.tokens.refresh_token;
|
||||
token.refresh_expire_in = res.tokens.refresh_expire_in;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
},
|
||||
async session({ session, token }: { session: Session; token: DefaultJWT }) {
|
||||
session.access_token = token.access_token as string;
|
||||
session.access_expire_in = token.access_expire_in as number;
|
||||
session.refresh_token = token.refresh_token as string;
|
||||
session.refresh_expire_in = token.refresh_expire_in as number;
|
||||
session.user = {
|
||||
id: token.id as number,
|
||||
username: token.username as string,
|
||||
fullname: token.fullname as string,
|
||||
email: token.email as string,
|
||||
role: token.role as Array<{ id: number; rol: string }>,
|
||||
};
|
||||
return session;
|
||||
},
|
||||
},
|
||||
} satisfies NextAuthConfig;
|
||||
|
||||
export default authConfig;
|
||||
17
apps/web/lib/auth.ts
Normal file
17
apps/web/lib/auth.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import NextAuth, { NextAuthConfig, Session } from 'next-auth';
|
||||
import authConfig from './auth.config';
|
||||
|
||||
export const {
|
||||
handlers,
|
||||
signIn,
|
||||
signOut,
|
||||
auth,
|
||||
}: {
|
||||
handlers: any;
|
||||
signIn: (provider?: string) => Promise<void>;
|
||||
signOut: () => Promise<void>;
|
||||
auth: () => Promise<Session | null>;
|
||||
} = NextAuth({
|
||||
session: { strategy: 'jwt' },
|
||||
...(authConfig as NextAuthConfig),
|
||||
});
|
||||
15
apps/web/lib/buildSearchParams.ts
Normal file
15
apps/web/lib/buildSearchParams.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export const buildSearchParams = <
|
||||
T extends Record<string, string | number | boolean | null | undefined>,
|
||||
>(
|
||||
params: T,
|
||||
): string => {
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
searchParams.append(key, String(value));
|
||||
}
|
||||
});
|
||||
|
||||
return searchParams.toString();
|
||||
};
|
||||
12
apps/web/lib/env.ts
Normal file
12
apps/web/lib/env.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createEnv } from '@t3-oss/env-nextjs';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
API_URL: z.string(),
|
||||
},
|
||||
client: {},
|
||||
runtimeEnv: {
|
||||
API_URL: process.env.API_URL,
|
||||
},
|
||||
});
|
||||
98
apps/web/lib/fetch.api.ts
Normal file
98
apps/web/lib/fetch.api.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
'use server';
|
||||
import { env } from '@/lib/env'; // Importamos la configuración de entorno validada
|
||||
import axios from 'axios';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Crear instancia de Axios con la URL base validada
|
||||
const fetchApi = axios.create({
|
||||
baseURL: env.API_URL, // Aquí usamos env.API_URL en vez de process.env.BACKEND_URL
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Interceptor para incluir el token automáticamente en las peticiones
|
||||
fetchApi.interceptors.request.use(async (config: any) => {
|
||||
try {
|
||||
// Importación dinámica para evitar la referencia circular
|
||||
const { auth } = await import('@/lib/auth');
|
||||
const session = await auth();
|
||||
const token = session?.access_token;
|
||||
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting auth token:', error);
|
||||
}
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
/**
|
||||
* Función para hacer peticiones con validación de respuesta
|
||||
* @param schema - Esquema de Zod para validar la respuesta
|
||||
* @param url - Endpoint a consultar
|
||||
* @param config - Configuración opcional de Axios
|
||||
* @returns [error, data] -> Devuelve el error como objeto estructurado si hay fallo, o los datos validados
|
||||
*/
|
||||
export const safeFetchApi = async <T extends z.ZodSchema<any>>(
|
||||
schema: T,
|
||||
url: string,
|
||||
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET',
|
||||
body?: any,
|
||||
): Promise<
|
||||
[{ type: string; message: string; details?: any } | null, z.infer<T> | null]
|
||||
> => {
|
||||
try {
|
||||
const response = await fetchApi({
|
||||
method,
|
||||
url,
|
||||
data: body,
|
||||
});
|
||||
|
||||
const parsed = schema.safeParse(response.data);
|
||||
|
||||
if (!parsed.success) {
|
||||
console.error('Validation Error Details:', {
|
||||
errors: parsed.error.errors,
|
||||
receivedData: response.data,
|
||||
expectedSchema: schema,
|
||||
data: response.data.data,
|
||||
});
|
||||
// console.error(parsed.error.errors)
|
||||
return [
|
||||
{
|
||||
type: 'VALIDATION_ERROR',
|
||||
message: 'Validation error',
|
||||
details: parsed.error.errors,
|
||||
},
|
||||
null,
|
||||
];
|
||||
}
|
||||
|
||||
return [null, parsed.data];
|
||||
} catch (error: any) {
|
||||
const errorDetails = {
|
||||
status: error.response?.status,
|
||||
statusText: error.response?.statusText,
|
||||
message: error.message,
|
||||
url: error.config?.url,
|
||||
method: error.config?.method,
|
||||
requestData: error.config?.data,
|
||||
responseData: error.response?.data,
|
||||
headers: error.config?.headers,
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
type: 'API_ERROR',
|
||||
message: error.response?.data?.message || 'Unknown API error',
|
||||
details: errorDetails,
|
||||
},
|
||||
null,
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
export { fetchApi };
|
||||
37
apps/web/lib/formData.ts
Normal file
37
apps/web/lib/formData.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export const objectToFormData = (data: Record<string, unknown>): FormData => {
|
||||
const formData = new FormData();
|
||||
|
||||
const appendValue = (key: string, value: unknown) => {
|
||||
if (value instanceof File || value instanceof Blob) {
|
||||
formData.append(key, value);
|
||||
} else if (Array.isArray(value)) {
|
||||
value.forEach((item, index) => {
|
||||
if (item instanceof File || item instanceof Blob) {
|
||||
formData.append(`${key}`, item);
|
||||
} else {
|
||||
formData.append(`${key}[${index}]`, item.toString());
|
||||
}
|
||||
});
|
||||
} else if (
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
!(value instanceof File) &&
|
||||
!(value instanceof Blob)
|
||||
) {
|
||||
// Handle nested objects
|
||||
Object.entries(value as Record<string, unknown>).forEach(
|
||||
([nestedKey, nestedValue]) => {
|
||||
appendValue(`${key}[${nestedKey}]`, nestedValue);
|
||||
},
|
||||
);
|
||||
} else if (value !== undefined && value !== null) {
|
||||
formData.append(key, String(value));
|
||||
}
|
||||
};
|
||||
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
appendValue(key, value);
|
||||
});
|
||||
|
||||
return formData;
|
||||
};
|
||||
5
apps/web/lib/index.ts
Normal file
5
apps/web/lib/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './buildSearchParams';
|
||||
export * from './env';
|
||||
export * from './fetch.api';
|
||||
export * from './formData';
|
||||
export * from './safeAction';
|
||||
3
apps/web/lib/safeAction.ts
Normal file
3
apps/web/lib/safeAction.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { createSafeActionClient } from 'next-safe-action';
|
||||
|
||||
export const safeAction = createSafeActionClient();
|
||||
Reference in New Issue
Block a user