feat: adição de novos ícones SVG e configuração do ambiente

- Adicionados ícones SVG para ChatGPT, Claude, Gemini e OpenRouter
- Implementados ícones para modos claro e escuro do ChatGPT
- Criado script de inicialização para PostgreSQL com extensão pgcrypto
- Adicionado script de configuração de ambiente que faz backup do .env
- Configurado tsconfig.json para TypeScript com opções de compilação
This commit is contained in:
Felipe Coutinho
2025-11-15 15:49:36 -03:00
commit ea0b8618e0
441 changed files with 53569 additions and 0 deletions

15
lib/auth/client.ts Normal file
View File

@@ -0,0 +1,15 @@
import { createAuthClient } from "better-auth/react";
const baseURL = process.env.BETTER_AUTH_URL?.replace(/\/$/, "");
export const authClient = createAuthClient({
...(baseURL ? { baseURL } : {}),
});
/**
* Indica se o login com Google está habilitado
* Baseado na variável de ambiente NEXT_PUBLIC_GOOGLE_OAUTH_ENABLED
*/
export const googleSignInAvailable = Boolean(
process.env.NEXT_PUBLIC_GOOGLE_OAUTH_ENABLED
);

119
lib/auth/config.ts Normal file
View File

@@ -0,0 +1,119 @@
/**
* Better Auth Configuration
*
* Configuração central de autenticação usando Better Auth.
* Suporta email/password e Google OAuth.
*/
import { seedDefaultCategoriesForUser } from "@/lib/categorias/defaults";
import { db, schema } from "@/lib/db";
import { ensureDefaultPagadorForUser } from "@/lib/pagadores/defaults";
import { normalizeNameFromEmail } from "@/lib/pagadores/utils";
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import type { GoogleProfile } from "better-auth/social-providers";
// ============================================================================
// GOOGLE OAUTH CONFIGURATION
// ============================================================================
const googleClientId = process.env.GOOGLE_CLIENT_ID;
const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
/**
* Extrai nome do usuário do perfil do Google com fallback hierárquico:
* 1. profile.name (nome completo)
* 2. profile.given_name + profile.family_name
* 3. Nome extraído do email
* 4. "Usuário" (fallback final)
*/
function getNameFromGoogleProfile(profile: GoogleProfile): string {
const fullName = profile.name?.trim();
if (fullName) return fullName;
const fromGivenFamily = [profile.given_name, profile.family_name]
.filter(Boolean)
.join(" ")
.trim();
if (fromGivenFamily) return fromGivenFamily;
const fromEmail = profile.email
? normalizeNameFromEmail(profile.email)
: undefined;
return fromEmail ?? "Usuário";
}
// ============================================================================
// BETTER AUTH INSTANCE
// ============================================================================
export const auth = betterAuth({
// Email/Password authentication
emailAndPassword: {
enabled: true,
autoSignIn: true,
},
// Database adapter (Drizzle + PostgreSQL)
database: drizzleAdapter(db, {
provider: "pg",
schema,
camelCase: true,
}),
// Google OAuth (se configurado)
socialProviders:
googleClientId && googleClientSecret
? {
google: {
clientId: googleClientId,
clientSecret: googleClientSecret,
mapProfileToUser: (profile) => ({
name: getNameFromGoogleProfile(profile),
email: profile.email,
image: profile.picture,
emailVerified: profile.email_verified,
}),
},
}
: undefined,
// Database hooks - Executados após eventos do DB
databaseHooks: {
user: {
create: {
/**
* Após criar novo usuário, inicializa:
* 1. Categorias padrão (Receitas/Despesas)
* 2. Pagador padrão (vinculado ao usuário)
*/
after: async (user) => {
// Se falhar aqui, o usuário já foi criado - considere usar queue para retry
try {
await seedDefaultCategoriesForUser(user.id);
await ensureDefaultPagadorForUser({
id: user.id,
name: user.name ?? undefined,
email: user.email ?? undefined,
image: user.image ?? undefined,
});
} catch (error) {
console.error(
"[Auth] Falha ao criar dados padrão do usuário:",
error
);
// TODO: Considere enfileirar para retry ou notificar admin
}
},
},
},
},
});
// Aviso em desenvolvimento se Google OAuth não estiver configurado
if (!googleClientId && process.env.NODE_ENV === "development") {
console.warn(
"[Auth] Google OAuth não configurado. Defina GOOGLE_CLIENT_ID e GOOGLE_CLIENT_SECRET."
);
}

69
lib/auth/server.ts Normal file
View File

@@ -0,0 +1,69 @@
/**
* Server-side authentication utilities
*
* This module consolidates server-side auth functions from:
* - /lib/get-user.tsx
* - /lib/get-user-id.tsx
* - /lib/get-user-session.tsx
*
* All functions in this module are server-side only and will redirect
* to /login if the user is not authenticated.
*/
import { auth } from "@/lib/auth/config";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
/**
* Gets the current authenticated user
* @returns User object
* @throws Redirects to /login if user is not authenticated
*/
export async function getUser() {
const session = await auth.api.getSession({ headers: await headers() });
if (!session?.user) {
redirect("/login");
}
return session.user;
}
/**
* Gets the current authenticated user ID
* @returns User ID string
* @throws Redirects to /login if user is not authenticated
*/
export async function getUserId() {
const session = await auth.api.getSession({ headers: await headers() });
if (!session?.user) {
redirect("/login");
}
return session.user.id;
}
/**
* Gets the current authenticated session
* @returns Full session object including user
* @throws Redirects to /login if user is not authenticated
*/
export async function getUserSession() {
const session = await auth.api.getSession({ headers: await headers() });
if (!session?.user) {
redirect("/login");
}
return session;
}
/**
* Gets the current session without requiring authentication
* @returns Session object or null if not authenticated
* @note This function does not redirect if user is not authenticated
*/
export async function getOptionalUserSession() {
return auth.api.getSession({ headers: await headers() });
}