Files
gridpilot.gg/adapters/admin/persistence/inmemory/InMemoryAdminUserRepository.ts
2026-01-24 02:05:43 +01:00

258 lines
7.0 KiB
TypeScript

import { AdminUser } from '@core/admin/domain/entities/AdminUser';
import { AdminUserRepository, StoredAdminUser, UserFilter, UserListQuery, UserListResult } from '@core/admin/domain/repositories/AdminUserRepository';
import { Email } from '@core/admin/domain/value-objects/Email';
import { UserId } from '@core/admin/domain/value-objects/UserId';
/**
* In-memory implementation of AdminUserRepository for testing and development
* Follows TDD - created with tests first
*/
export class InMemoryAdminUserRepository implements AdminUserRepository {
private storage: Map<string, StoredAdminUser> = new Map();
async findById(id: UserId): Promise<AdminUser | null> {
const stored = this.storage.get(id.value);
return stored ? this.fromStored(stored) : null;
}
async findByEmail(email: Email): Promise<AdminUser | null> {
for (const stored of this.storage.values()) {
if (stored.email === email.value) {
return this.fromStored(stored);
}
}
return null;
}
async emailExists(email: Email): Promise<boolean> {
for (const stored of this.storage.values()) {
if (stored.email === email.value) {
return true;
}
}
return false;
}
async existsById(id: UserId): Promise<boolean> {
return this.storage.has(id.value);
}
async existsByEmail(email: Email): Promise<boolean> {
return this.emailExists(email);
}
async list(query?: UserListQuery): Promise<UserListResult> {
let users: AdminUser[] = [];
// Get all users
for (const stored of this.storage.values()) {
users.push(this.fromStored(stored));
}
// Apply filters
if (query?.filter) {
users = users.filter(user => {
if (query.filter?.role && !user.roles.some(r => r.equals(query.filter!.role!))) {
return false;
}
if (query.filter?.status && !user.status.equals(query.filter.status)) {
return false;
}
if (query.filter?.email && !user.email.equals(query.filter.email)) {
return false;
}
if (query.filter?.search) {
const search = query.filter.search.toLowerCase();
const matchesEmail = user.email.value.toLowerCase().includes(search);
const matchesDisplayName = user.displayName.toLowerCase().includes(search);
if (!matchesEmail && !matchesDisplayName) {
return false;
}
}
return true;
});
}
const total = users.length;
// Apply sorting
if (query?.sort) {
const { field, direction } = query.sort;
users.sort((a, b) => {
let aVal: string | number | Date;
let bVal: string | number | Date;
switch (field) {
case 'email':
aVal = a.email.value;
bVal = b.email.value;
break;
case 'displayName':
aVal = a.displayName;
bVal = b.displayName;
break;
case 'createdAt':
aVal = a.createdAt;
bVal = b.createdAt;
break;
case 'lastLoginAt':
aVal = a.lastLoginAt || 0;
bVal = b.lastLoginAt || 0;
break;
case 'status':
aVal = a.status.value;
bVal = b.status.value;
break;
default:
return 0;
}
if (aVal < bVal) return direction === 'asc' ? -1 : 1;
if (aVal > bVal) return direction === 'asc' ? 1 : -1;
return 0;
});
}
// Apply pagination
const page = query?.pagination?.page || 1;
const limit = query?.pagination?.limit || 10;
const totalPages = Math.ceil(total / limit);
const start = (page - 1) * limit;
const end = start + limit;
const paginatedUsers = users.slice(start, end);
return {
users: paginatedUsers,
total,
page,
limit,
totalPages,
};
}
async count(filter?: UserFilter): Promise<number> {
let count = 0;
for (const stored of this.storage.values()) {
const user = this.fromStored(stored);
if (filter?.role && !user.roles.some(r => r.equals(filter.role!))) {
continue;
}
if (filter?.status && !user.status.equals(filter.status)) {
continue;
}
if (filter?.email && !user.email.equals(filter.email)) {
continue;
}
if (filter?.search) {
const search = filter.search.toLowerCase();
const matchesEmail = user.email.value.toLowerCase().includes(search);
const matchesDisplayName = user.displayName.toLowerCase().includes(search);
if (!matchesEmail && !matchesDisplayName) {
continue;
}
}
count++;
}
return count;
}
async create(user: AdminUser): Promise<AdminUser> {
// Check for duplicate email
if (await this.emailExists(user.email)) {
throw new Error('Email already exists');
}
// Check for duplicate ID
if (this.storage.has(user.id.value)) {
throw new Error('User ID already exists');
}
const stored = this.toStored(user);
this.storage.set(user.id.value, stored);
return user;
}
async update(user: AdminUser): Promise<AdminUser> {
// Check if user exists
if (!this.storage.has(user.id.value)) {
throw new Error('User not found');
}
// Check for duplicate email (excluding current user)
for (const [id, stored] of this.storage.entries()) {
if (id !== user.id.value && stored.email === user.email.value) {
throw new Error('Email already exists');
}
}
const stored = this.toStored(user);
this.storage.set(user.id.value, stored);
return user;
}
async delete(id: UserId): Promise<void> {
if (!this.storage.has(id.value)) {
throw new Error('User not found');
}
this.storage.delete(id.value);
}
toStored(user: AdminUser): StoredAdminUser {
const stored: StoredAdminUser = {
id: user.id.value,
email: user.email.value,
roles: user.roles.map(r => r.value),
status: user.status.value,
displayName: user.displayName,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
};
if (user.lastLoginAt !== undefined) {
stored.lastLoginAt = user.lastLoginAt;
}
if (user.primaryDriverId !== undefined) {
stored.primaryDriverId = user.primaryDriverId;
}
return stored;
}
fromStored(stored: StoredAdminUser): AdminUser {
const props: {
id: string;
email: string;
roles: string[];
status: string;
displayName: string;
createdAt: Date;
updatedAt: Date;
lastLoginAt?: Date;
primaryDriverId?: string;
} = {
id: stored.id,
email: stored.email,
roles: stored.roles,
status: stored.status,
displayName: stored.displayName,
createdAt: stored.createdAt,
updatedAt: stored.updatedAt,
};
if (stored.lastLoginAt !== undefined) {
props.lastLoginAt = stored.lastLoginAt;
}
if (stored.primaryDriverId !== undefined) {
props.primaryDriverId = stored.primaryDriverId;
}
return AdminUser.rehydrate(props);
}
}