211 lines
8.0 KiB
TypeScript
211 lines
8.0 KiB
TypeScript
import { db, pool } from "../db/index";
|
|
import { users, applications, userApplications, profiles, crmPartners, tenants, tenantUsers, type User, type InsertUser, type Application, type InsertApplication } from "@shared/schema";
|
|
import { eq, and } from "drizzle-orm";
|
|
import session from "express-session";
|
|
import connectPg from "connect-pg-simple";
|
|
|
|
const PostgresSessionStore = connectPg(session);
|
|
|
|
export interface IStorage {
|
|
sessionStore: session.Store;
|
|
|
|
getUser(id: string): Promise<User | undefined>;
|
|
getUserByUsername(username: string, tenantId?: number): Promise<User | undefined>;
|
|
createUser(user: InsertUser): Promise<User>;
|
|
getEnrichedUser(user: User): Promise<any>;
|
|
|
|
getApplications(): Promise<Application[]>;
|
|
getApplication(id: string): Promise<Application | undefined>;
|
|
createApplication(app: InsertApplication): Promise<Application>;
|
|
updateApplication(id: string, app: Partial<InsertApplication>): Promise<Application | undefined>;
|
|
deleteApplication(id: string): Promise<boolean>;
|
|
|
|
getUserApplications(userId: string): Promise<Application[]>;
|
|
assignApplicationToUser(userId: string, applicationId: string): Promise<void>;
|
|
removeApplicationFromUser(userId: string, applicationId: string): Promise<void>;
|
|
getTenant(id: number): Promise<{ id: number; name: string; slug: string; tenantType?: string; plan?: string; status?: string } | undefined>;
|
|
getTenants(): Promise<{ id: number; name: string; slug: string }[]>;
|
|
}
|
|
|
|
export class DatabaseStorage implements IStorage {
|
|
sessionStore: session.Store;
|
|
|
|
constructor() {
|
|
console.log('[Storage] Inicializando session store...');
|
|
this.sessionStore = new PostgresSessionStore({
|
|
pool,
|
|
createTableIfMissing: true,
|
|
tableName: 'session',
|
|
pruneSessionInterval: 60 * 60, // 1 hora
|
|
});
|
|
|
|
// Log de erros do session store
|
|
this.sessionStore.on('error', (err: any) => {
|
|
console.error('[SessionStore] Erro:', err.message);
|
|
});
|
|
|
|
console.log('[Storage] Session store inicializado');
|
|
}
|
|
|
|
async getUser(id: string): Promise<User | undefined> {
|
|
console.log(`[Storage] Buscando usuário por ID: ${id}`);
|
|
const [user] = await db.select().from(users).where(eq(users.id, id));
|
|
console.log(`[Storage] Usuário ${user ? 'encontrado' : 'não encontrado'}: ${id}`);
|
|
return user;
|
|
}
|
|
|
|
// CORREÇÃO: getUserByUsername com validação de tenant
|
|
async getUserByUsername(username: string, tenantId?: number): Promise<User | undefined> {
|
|
console.log(`[Storage] Buscando usuário por username: ${username}`);
|
|
const [user] = await db.select().from(users).where(eq(users.username, username));
|
|
console.log(`[Storage] Usuário ${user ? 'encontrado' : 'não encontrado'}: ${username}`);
|
|
|
|
// Se tenantId foi passado, validar que usuário pertence a esse tenant
|
|
if (tenantId && user) {
|
|
console.log(`[Storage] Validando tenant ${tenantId} para usuário: ${username}`);
|
|
const [tenantUser] = await db.select()
|
|
.from(tenantUsers)
|
|
.where(and(
|
|
eq(tenantUsers.userId, user.id),
|
|
eq(tenantUsers.tenantId, tenantId)
|
|
));
|
|
if (!tenantUser) {
|
|
console.log(`[Storage] Usuário ${username} não pertence ao tenant ${tenantId}`);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
return user;
|
|
}
|
|
|
|
async createUser(insertUser: InsertUser): Promise<User> {
|
|
const [user] = await db.insert(users).values(insertUser).returning();
|
|
return user;
|
|
}
|
|
|
|
async getApplications(): Promise<Application[]> {
|
|
return await db.select().from(applications);
|
|
}
|
|
|
|
async getApplication(id: string): Promise<Application | undefined> {
|
|
const [app] = await db.select().from(applications).where(eq(applications.id, id));
|
|
return app;
|
|
}
|
|
|
|
async createApplication(app: InsertApplication): Promise<Application> {
|
|
const [newApp] = await db.insert(applications).values(app).returning();
|
|
return newApp;
|
|
}
|
|
|
|
async updateApplication(id: string, app: Partial<InsertApplication>): Promise<Application | undefined> {
|
|
const [updated] = await db.update(applications)
|
|
.set(app)
|
|
.where(eq(applications.id, id))
|
|
.returning();
|
|
return updated;
|
|
}
|
|
|
|
async deleteApplication(id: string): Promise<boolean> {
|
|
const result = await db.delete(applications).where(eq(applications.id, id));
|
|
return result.rowCount ? result.rowCount > 0 : false;
|
|
}
|
|
|
|
async getUserApplications(userId: string): Promise<Application[]> {
|
|
const result = await db
|
|
.select({ application: applications })
|
|
.from(userApplications)
|
|
.innerJoin(applications, eq(userApplications.applicationId, applications.id))
|
|
.where(eq(userApplications.userId, userId));
|
|
return result.map(r => r.application);
|
|
}
|
|
|
|
async assignApplicationToUser(userId: string, applicationId: string): Promise<void> {
|
|
await db.insert(userApplications).values({ userId, applicationId }).onConflictDoNothing();
|
|
}
|
|
|
|
async removeApplicationFromUser(userId: string, applicationId: string): Promise<void> {
|
|
await db.delete(userApplications).where(
|
|
and(
|
|
eq(userApplications.userId, userId),
|
|
eq(userApplications.applicationId, applicationId)
|
|
)
|
|
);
|
|
}
|
|
|
|
async getEnrichedUser(user: User): Promise<any> {
|
|
console.log(`[Storage] Iniciando enriquecimento do usuário: ${user.id}`);
|
|
const enriched: any = { ...user };
|
|
|
|
if (user.profileId) {
|
|
console.log(`[Storage] Buscando profile: ${user.profileId}`);
|
|
const [profile] = await db.select().from(profiles).where(eq(profiles.id, user.profileId));
|
|
if (profile) {
|
|
enriched.profile = profile;
|
|
enriched.allowedModules = profile.allowedModules || [];
|
|
console.log(`[Storage] Profile encontrado: ${profile.name}`);
|
|
}
|
|
}
|
|
|
|
if (user.partnerId) {
|
|
console.log(`[Storage] Buscando partner: ${user.partnerId}`);
|
|
const [partner] = await db.select().from(crmPartners).where(eq(crmPartners.id, user.partnerId));
|
|
if (partner) {
|
|
enriched.partner = partner;
|
|
console.log(`[Storage] Partner encontrado: ${partner.name}`);
|
|
}
|
|
}
|
|
|
|
// Buscar tenant do usuário (apenas campos seguros para o cliente)
|
|
console.log(`[Storage] Buscando tenantUser para: ${user.id}`);
|
|
const [tenantUser] = await db.select().from(tenantUsers).where(eq(tenantUsers.userId, user.id));
|
|
if (tenantUser) {
|
|
console.log(`[Storage] TenantUser encontrado, buscando tenant: ${tenantUser.tenantId}`);
|
|
const [tenant] = await db.select().from(tenants).where(eq(tenants.id, tenantUser.tenantId));
|
|
if (tenant) {
|
|
// Expor apenas campos seguros do tenant
|
|
enriched.tenant = {
|
|
id: tenant.id,
|
|
name: tenant.name,
|
|
tenantType: tenant.tenantType,
|
|
plan: tenant.plan,
|
|
status: tenant.status
|
|
};
|
|
enriched.tenantId = tenant.id;
|
|
enriched.tenantType = tenant.tenantType;
|
|
enriched.tenantRole = tenantUser.role;
|
|
enriched.isOwner = tenantUser.isOwner === "true";
|
|
console.log(`[Storage] Tenant enriquecido: ${tenant.name}`);
|
|
}
|
|
} else {
|
|
console.log(`[Storage] Usuário sem tenant associado: ${user.id}`);
|
|
}
|
|
|
|
console.log(`[Storage] Enriquecimento completo: ${user.id}`);
|
|
return enriched;
|
|
}
|
|
|
|
// CORREÇÃO: getTenant para buscar tenant específico
|
|
async getTenant(id: number): Promise<{ id: number; name: string; slug: string; tenantType?: string; plan?: string; status?: string } | undefined> {
|
|
const [tenant] = await db.select({
|
|
id: tenants.id,
|
|
name: tenants.name,
|
|
slug: tenants.slug,
|
|
tenantType: tenants.tenantType,
|
|
plan: tenants.plan,
|
|
status: tenants.status
|
|
}).from(tenants).where(eq(tenants.id, id));
|
|
return tenant;
|
|
}
|
|
|
|
async getTenants(): Promise<{ id: number; name: string; slug: string }[]> {
|
|
const result = await db.select({
|
|
id: tenants.id,
|
|
name: tenants.name,
|
|
slug: tenants.slug
|
|
}).from(tenants);
|
|
return result.map(t => ({ id: t.id, name: t.name || '', slug: t.slug || '' }));
|
|
}
|
|
}
|
|
|
|
export const storage = new DatabaseStorage();
|