import { User, InsertUser, ApiToken, InsertApiToken, LdapConnection, InsertLdapConnection, AdUser, InsertAdUser, AdGroup, InsertAdGroup, AdOrgUnit, InsertAdOrgUnit, AdComputer, InsertAdComputer, AdDomain, InsertAdDomain, Role, ApiQuery, users, apiTokens, ldapConnections, adUsers, adGroups, adOrgUnits, adComputers, adDomains, roles } from "@shared/schema"; import session from "express-session"; import createMemoryStore from "memorystore"; import crypto from "crypto"; import { db } from "./db"; import { eq, and, type SQL } from "drizzle-orm"; import connectPg from "connect-pg-simple"; import { Pool } from "@neondatabase/serverless"; import { applyQueryOptions } from "./query-parser"; import debugLib from 'debug'; import { getCached, setCached, CACHE_TTL, invalidateCache, invalidateCachePattern } from './cache'; const debug = debugLib('api:storage'); // Memory store for sessions const MemoryStore = createMemoryStore(session); // PostgreSQL session store const pool = new Pool({ connectionString: process.env.DATABASE_URL, }); const PostgresStore = connectPg(session); export interface IStorage { // User management getUser(id: number): Promise; getUserByUsername(username: string): Promise; createUser(user: InsertUser): Promise; updateUser(id: number, user: Partial): Promise; deleteUser(id: number): Promise; listUsers(): Promise; // Role management getRole(id: number): Promise; getDefaultRole(): Promise; // API Token management getApiToken(id: number): Promise; getApiTokenByToken(token: string): Promise; createApiToken(token: InsertApiToken): Promise; deleteApiToken(id: number): Promise; listApiTokensByUserId(userId: number): Promise; // LDAP Connection management getLdapConnection(id: number): Promise; createLdapConnection(connection: InsertLdapConnection): Promise; updateLdapConnection(id: number, connection: Partial): Promise; deleteLdapConnection(id: number): Promise; listLdapConnections(): Promise; // AD Users getAdUser(id: number): Promise; createAdUser(user: InsertAdUser): Promise; updateAdUser(id: number, user: Partial): Promise; deleteAdUser(id: number): Promise; listAdUsers(connectionId: number, query?: any): Promise; // AD Groups getAdGroup(id: number): Promise; createAdGroup(group: InsertAdGroup): Promise; updateAdGroup(id: number, group: Partial): Promise; deleteAdGroup(id: number): Promise; listAdGroups(connectionId: number, query?: any): Promise; // AD Organizational Units getAdOrgUnit(id: number): Promise; createAdOrgUnit(ou: InsertAdOrgUnit): Promise; updateAdOrgUnit(id: number, ou: Partial): Promise; deleteAdOrgUnit(id: number): Promise; listAdOrgUnits(connectionId: number, query?: any): Promise; // AD Computers getAdComputer(id: number): Promise; createAdComputer(computer: InsertAdComputer): Promise; updateAdComputer(id: number, computer: Partial): Promise; deleteAdComputer(id: number): Promise; listAdComputers(connectionId: number, query?: any): Promise; // AD Domains getAdDomain(id: number): Promise; createAdDomain(domain: InsertAdDomain): Promise; updateAdDomain(id: number, domain: Partial): Promise; deleteAdDomain(id: number): Promise; listAdDomains(connectionId: number, query?: any): Promise; // Session store sessionStore: any; } // Database Storage implementation export class DatabaseStorage implements IStorage { sessionStore: any; constructor() { this.sessionStore = new PostgresStore({ pool, createTableIfMissing: true }); } // User management async getUser(id: number): Promise { const result = await db.select().from(users).where(eq(users.id, id)); return result.length > 0 ? result[0] : undefined; } async getUserByUsername(username: string): Promise { const result = await db.select().from(users).where(eq(users.username, username)); return result.length > 0 ? result[0] : undefined; } async createUser(insertUser: InsertUser): Promise { const result = await db.insert(users).values(insertUser).returning(); return result[0]; } async updateUser(id: number, userData: Partial): Promise { const result = await db.update(users).set(userData).where(eq(users.id, id)).returning(); return result.length > 0 ? result[0] : undefined; } async deleteUser(id: number): Promise { const result = await db.delete(users).where(eq(users.id, id)).returning({ id: users.id }); return result.length > 0; } async listUsers(): Promise { return db.select().from(users); } // Role management async getRole(id: number): Promise { const result = await db.select().from(roles).where(eq(roles.id, id)); return result.length > 0 ? result[0] : undefined; } async getDefaultRole(): Promise { const result = await db.select().from(roles).where(eq(roles.isDefault, true)); return result.length > 0 ? result[0] : undefined; } // API Token management async getApiToken(id: number): Promise { const result = await db.select().from(apiTokens).where(eq(apiTokens.id, id)); return result.length > 0 ? result[0] : undefined; } async getApiTokenByToken(token: string): Promise { const result = await db.select().from(apiTokens).where(eq(apiTokens.token, token)); return result.length > 0 ? result[0] : undefined; } async createApiToken(insertToken: InsertApiToken): Promise { const result = await db.insert(apiTokens).values(insertToken).returning(); return result[0]; } async deleteApiToken(id: number): Promise { const result = await db.delete(apiTokens).where(eq(apiTokens.id, id)).returning({ id: apiTokens.id }); return result.length > 0; } async listApiTokensByUserId(userId: number): Promise { return db.select().from(apiTokens).where(eq(apiTokens.userId, userId)); } // LDAP Connection management async getLdapConnection(id: number): Promise { const result = await db.select().from(ldapConnections).where(eq(ldapConnections.id, id)); return result.length > 0 ? result[0] : undefined; } async createLdapConnection(insertConnection: InsertLdapConnection): Promise { const result = await db.insert(ldapConnections).values(insertConnection).returning(); return result[0]; } async updateLdapConnection(id: number, connectionData: Partial): Promise { const result = await db.update(ldapConnections).set(connectionData).where(eq(ldapConnections.id, id)).returning(); return result.length > 0 ? result[0] : undefined; } async deleteLdapConnection(id: number): Promise { const result = await db.delete(ldapConnections).where(eq(ldapConnections.id, id)).returning({ id: ldapConnections.id }); return result.length > 0; } async listLdapConnections(): Promise { return db.select().from(ldapConnections); } // AD Users async getAdUser(id: number): Promise { const result = await db.select().from(adUsers).where(eq(adUsers.id, id)); return result.length > 0 ? result[0] : undefined; } async createAdUser(user: InsertAdUser): Promise { const result = await db.insert(adUsers).values(user).returning(); return result[0]; } async updateAdUser(id: number, userData: Partial): Promise { const result = await db.update(adUsers).set(userData).where(eq(adUsers.id, id)).returning(); return result.length > 0 ? result[0] : undefined; } async deleteAdUser(id: number): Promise { const result = await db.delete(adUsers).where(eq(adUsers.id, id)).returning({ id: adUsers.id }); return result.length > 0; } async listAdUsers(connectionId: number, query?: ApiQuery): Promise { const cacheKey = `adUsers:${connectionId}:${JSON.stringify(query || {})}`; // Try to get from cache first const cachedData = await getCached(cacheKey); if (cachedData) { debug(`Cache hit for ${cacheKey}`); return cachedData; } let baseQuery = db.select().from(adUsers) .where(eq(adUsers.connectionId, connectionId)); if (query) { // Apply advanced filtering using the query parser const { whereClause, orderClauses, limit, offset, selectedFields } = applyQueryOptions(adUsers, query); // Apply where conditions if any if (whereClause) { baseQuery = baseQuery.where(whereClause); } // Apply ordering if any if (orderClauses.length > 0) { baseQuery = baseQuery.orderBy(...orderClauses); } // Apply pagination if specified if (limit !== undefined) { baseQuery = baseQuery.limit(limit); } if (offset !== undefined) { baseQuery = baseQuery.offset(offset); } // Execute the query const users = await baseQuery; // Handle field selection if specified if (selectedFields.length > 0) { const result = users.map(user => { const filtered: Partial = { id: user.id }; selectedFields.forEach(field => { if (field in user) { filtered[field as keyof AdUser] = user[field as keyof AdUser]; } }); return filtered as AdUser; }); // Cache the result await setCached(cacheKey, result, CACHE_TTL.MEDIUM); return result; } // Cache the result await setCached(cacheKey, users, CACHE_TTL.MEDIUM); return users; } // No query params, just return all results const users = await baseQuery; await setCached(cacheKey, users, CACHE_TTL.MEDIUM); return users; } // AD Groups async getAdGroup(id: number): Promise { const result = await db.select().from(adGroups).where(eq(adGroups.id, id)); return result.length > 0 ? result[0] : undefined; } async createAdGroup(group: InsertAdGroup): Promise { const result = await db.insert(adGroups).values(group).returning(); return result[0]; } async updateAdGroup(id: number, groupData: Partial): Promise { const result = await db.update(adGroups).set(groupData).where(eq(adGroups.id, id)).returning(); return result.length > 0 ? result[0] : undefined; } async deleteAdGroup(id: number): Promise { const result = await db.delete(adGroups).where(eq(adGroups.id, id)).returning({ id: adGroups.id }); return result.length > 0; } async listAdGroups(connectionId: number, query?: ApiQuery): Promise { const cacheKey = `adGroups:${connectionId}:${JSON.stringify(query || {})}`; // Try to get from cache first const cachedData = await getCached(cacheKey); if (cachedData) { debug(`Cache hit for ${cacheKey}`); return cachedData; } let baseQuery = db.select().from(adGroups) .where(eq(adGroups.connectionId, connectionId)); if (query) { // Apply advanced filtering using the query parser const { whereClause, orderClauses, limit, offset, selectedFields } = applyQueryOptions(adGroups, query); // Apply where conditions if any if (whereClause) { baseQuery = baseQuery.where(whereClause); } // Apply ordering if any if (orderClauses.length > 0) { baseQuery = baseQuery.orderBy(...orderClauses); } // Apply pagination if specified if (limit !== undefined) { baseQuery = baseQuery.limit(limit); } if (offset !== undefined) { baseQuery = baseQuery.offset(offset); } // Execute the query const groups = await baseQuery; // Handle field selection if specified if (selectedFields.length > 0) { const result = groups.map(group => { const filtered: Partial = { id: group.id }; selectedFields.forEach(field => { if (field in group) { filtered[field as keyof AdGroup] = group[field as keyof AdGroup]; } }); return filtered as AdGroup; }); // Cache the result await setCached(cacheKey, result, CACHE_TTL.MEDIUM); return result; } // Cache the result await setCached(cacheKey, groups, CACHE_TTL.MEDIUM); return groups; } // No query params, just return all results const groups = await baseQuery; await setCached(cacheKey, groups, CACHE_TTL.MEDIUM); return groups; } // AD Organizational Units async getAdOrgUnit(id: number): Promise { const result = await db.select().from(adOrgUnits).where(eq(adOrgUnits.id, id)); return result.length > 0 ? result[0] : undefined; } async createAdOrgUnit(ou: InsertAdOrgUnit): Promise { const result = await db.insert(adOrgUnits).values(ou).returning(); return result[0]; } async updateAdOrgUnit(id: number, ouData: Partial): Promise { const result = await db.update(adOrgUnits).set(ouData).where(eq(adOrgUnits.id, id)).returning(); return result.length > 0 ? result[0] : undefined; } async deleteAdOrgUnit(id: number): Promise { const result = await db.delete(adOrgUnits).where(eq(adOrgUnits.id, id)).returning({ id: adOrgUnits.id }); return result.length > 0; } async listAdOrgUnits(connectionId: number, query?: any): Promise { let adOrgUnitsQuery = db.select().from(adOrgUnits).where(eq(adOrgUnits.connectionId, connectionId)); // Handle filtering logic (similar to listAdUsers) if (query && query.select) { const orgUnits = await adOrgUnitsQuery; const properties = query.select.split(','); return orgUnits.map(ou => { const result: any = { id: ou.id }; properties.forEach((prop: string) => { if ((ou as any)[prop] !== undefined) { result[prop] = (ou as any)[prop]; } }); return result as AdOrgUnit; }); } return adOrgUnitsQuery; } // AD Computers async getAdComputer(id: number): Promise { const result = await db.select().from(adComputers).where(eq(adComputers.id, id)); return result.length > 0 ? result[0] : undefined; } async createAdComputer(computer: InsertAdComputer): Promise { const result = await db.insert(adComputers).values(computer).returning(); return result[0]; } async updateAdComputer(id: number, computerData: Partial): Promise { const result = await db.update(adComputers).set(computerData).where(eq(adComputers.id, id)).returning(); return result.length > 0 ? result[0] : undefined; } async deleteAdComputer(id: number): Promise { const result = await db.delete(adComputers).where(eq(adComputers.id, id)).returning({ id: adComputers.id }); return result.length > 0; } async listAdComputers(connectionId: number, query?: any): Promise { let adComputersQuery = db.select().from(adComputers).where(eq(adComputers.connectionId, connectionId)); // Handle filtering logic (similar to listAdUsers) if (query && query.select) { const computers = await adComputersQuery; const properties = query.select.split(','); return computers.map(computer => { const result: any = { id: computer.id }; properties.forEach((prop: string) => { if ((computer as any)[prop] !== undefined) { result[prop] = (computer as any)[prop]; } }); return result as AdComputer; }); } return adComputersQuery; } // AD Domains async getAdDomain(id: number): Promise { const result = await db.select().from(adDomains).where(eq(adDomains.id, id)); return result.length > 0 ? result[0] : undefined; } async createAdDomain(domain: InsertAdDomain): Promise { const result = await db.insert(adDomains).values(domain).returning(); return result[0]; } async updateAdDomain(id: number, domainData: Partial): Promise { const result = await db.update(adDomains).set(domainData).where(eq(adDomains.id, id)).returning(); return result.length > 0 ? result[0] : undefined; } async deleteAdDomain(id: number): Promise { const result = await db.delete(adDomains).where(eq(adDomains.id, id)).returning({ id: adDomains.id }); return result.length > 0; } async listAdDomains(connectionId: number, query?: any): Promise { let adDomainsQuery = db.select().from(adDomains).where(eq(adDomains.connectionId, connectionId)); // Handle filtering logic (similar to listAdUsers) if (query && query.select) { const domains = await adDomainsQuery; const properties = query.select.split(','); return domains.map(domain => { const result: any = { id: domain.id }; properties.forEach((prop: string) => { if ((domain as any)[prop] !== undefined) { result[prop] = (domain as any)[prop]; } }); return result as AdDomain; }); } return adDomainsQuery; } } export const storage = new DatabaseStorage();