258 lines
7.0 KiB
TypeScript
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);
|
|
}
|
|
}
|