Files
gridpilot.gg/adapters/identity/persistence/inmemory/InMemoryUserRepository.ts
2025-12-15 22:39:17 +01:00

117 lines
4.2 KiB
TypeScript

/**
* In-Memory User Repository
*
* Stores users in memory for demo/development purposes.
*/
import { Logger } from '@gridpilot/core/shared/application';
import type { IUserRepository, StoredUser } from '../../domain/repositories/IUserRepository';
export class InMemoryUserRepository implements IUserRepository {
private users: Map<string, StoredUser> = new Map();
private emailIndex: Map<string, string> = new Map(); // email -> userId
private readonly logger: Logger;
constructor(logger: Logger, initialUsers: StoredUser[] = []) {
this.logger = logger;
this.logger.info('InMemoryUserRepository initialized.');
for (const user of initialUsers) {
this.users.set(user.id, user);
this.emailIndex.set(user.email.toLowerCase(), user.id);
this.logger.debug(`Seeded user: ${user.id} (${user.email}).`);
}
}
async findByEmail(email: string): Promise<StoredUser | null> {
this.logger.debug(`Finding user by email: ${email}`);
try {
const userId = this.emailIndex.get(email.toLowerCase());
if (!userId) {
this.logger.warn(`User with email ${email} not found.`);
return null;
}
const user = this.users.get(userId) ?? null;
if (user) {
this.logger.info(`Found user by email: ${email}.`);
} else {
this.logger.warn(`User with ID ${userId} (from email index) not found.`);
}
return user;
} catch (error) {
this.logger.error(`Error finding user by email ${email}:`, error);
throw error;
}
}
async findById(id: string): Promise<StoredUser | null> {
this.logger.debug(`Finding user by id: ${id}`);
try {
const user = this.users.get(id) ?? null;
if (user) {
this.logger.info(`Found user: ${id}.`);
} else {
this.logger.warn(`User with id ${id} not found.`);
}
return user;
} catch (error) {
this.logger.error(`Error finding user by id ${id}:`, error);
throw error;
}
}
async create(user: StoredUser): Promise<StoredUser> {
this.logger.debug(`Creating user: ${user.id} with email: ${user.email}`);
try {
if (this.emailIndex.has(user.email.toLowerCase())) {
this.logger.warn(`Email ${user.email} already exists.`);
throw new Error('Email already exists');
}
this.users.set(user.id, user);
this.emailIndex.set(user.email.toLowerCase(), user.id);
this.logger.info(`User ${user.id} (${user.email}) created successfully.`);
return user;
} catch (error) {
this.logger.error(`Error creating user ${user.id} (${user.email}):`, error);
throw error;
}
}
async update(user: StoredUser): Promise<StoredUser> {
this.logger.debug(`Updating user: ${user.id} with email: ${user.email}`);
try {
const existing = this.users.get(user.id);
if (!existing) {
this.logger.warn(`User with ID ${user.id} not found for update.`);
throw new Error('User not found');
}
// If email changed, update index
if (existing.email.toLowerCase() !== user.email.toLowerCase()) {
if (this.emailIndex.has(user.email.toLowerCase()) && this.emailIndex.get(user.email.toLowerCase()) !== user.id) {
this.logger.warn(`Cannot update user ${user.id} to email ${user.email} as it's already taken.`);
throw new Error('Email already exists for another user');
}
this.logger.debug(`Updating email index from ${existing.email} to ${user.email}.`);
this.emailIndex.delete(existing.email.toLowerCase());
this.emailIndex.set(user.email.toLowerCase(), user.id);
}
this.users.set(user.id, user);
this.logger.info(`User ${user.id} (${user.email}) updated successfully.`);
return user;
} catch (error) {
this.logger.error(`Error updating user ${user.id} (${user.email}):`, error);
throw error;
}
}
async emailExists(email: string): Promise<boolean> {
this.logger.debug(`Checking existence of email: ${email}`);
try {
const exists = this.emailIndex.has(email.toLowerCase());
this.logger.debug(`Email ${email} exists: ${exists}.`);
return exists;
} catch (error) {
this.logger.error(`Error checking existence of email ${email}:`, error);
throw error;
}
}
}