admin area
This commit is contained in:
@@ -0,0 +1,790 @@
|
||||
import { InMemoryAdminUserRepository } from './InMemoryAdminUserRepository';
|
||||
import { AdminUser } from '../../domain/entities/AdminUser';
|
||||
import { UserRole } from '../../domain/value-objects/UserRole';
|
||||
import { UserStatus } from '../../domain/value-objects/UserStatus';
|
||||
|
||||
describe('InMemoryAdminUserRepository', () => {
|
||||
describe('TDD - Test First', () => {
|
||||
let repository: InMemoryAdminUserRepository;
|
||||
|
||||
beforeEach(() => {
|
||||
repository = new InMemoryAdminUserRepository();
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new user', async () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await repository.create(user);
|
||||
|
||||
// Assert
|
||||
expect(result).toStrictEqual(user);
|
||||
const found = await repository.findById(user.id);
|
||||
expect(found).toStrictEqual(user);
|
||||
});
|
||||
|
||||
it('should throw error when creating user with duplicate email', async () => {
|
||||
// Arrange
|
||||
const user1 = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'test@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const user2 = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'test@example.com',
|
||||
displayName: 'User 2',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user1);
|
||||
|
||||
// Act & Assert
|
||||
await expect(repository.create(user2)).rejects.toThrow('Email already exists');
|
||||
});
|
||||
|
||||
it('should throw error when creating user with duplicate ID', async () => {
|
||||
// Arrange
|
||||
const user1 = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const user2 = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'User 2',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user1);
|
||||
|
||||
// Act & Assert
|
||||
await expect(repository.create(user2)).rejects.toThrow('User ID already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should find user by ID', async () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user);
|
||||
|
||||
// Act
|
||||
const found = await repository.findById(user.id);
|
||||
|
||||
// Assert
|
||||
expect(found).toStrictEqual(user);
|
||||
});
|
||||
|
||||
it('should return null for non-existent user', async () => {
|
||||
// Arrange
|
||||
const nonExistentId = AdminUser.create({
|
||||
id: 'non-existent',
|
||||
email: 'dummy@example.com',
|
||||
displayName: 'Dummy',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
}).id;
|
||||
|
||||
// Act
|
||||
const found = await repository.findById(nonExistentId);
|
||||
|
||||
// Assert
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByEmail', () => {
|
||||
it('should find user by email', async () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user);
|
||||
|
||||
// Act
|
||||
const found = await repository.findByEmail(user.email);
|
||||
|
||||
// Assert
|
||||
expect(found).toStrictEqual(user);
|
||||
});
|
||||
|
||||
it('should return null for non-existent email', async () => {
|
||||
// Arrange
|
||||
const nonExistentEmail = AdminUser.create({
|
||||
id: 'dummy',
|
||||
email: 'non-existent@example.com',
|
||||
displayName: 'Dummy',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
}).email;
|
||||
|
||||
// Act
|
||||
const found = await repository.findByEmail(nonExistentEmail);
|
||||
|
||||
// Assert
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('emailExists', () => {
|
||||
it('should return true when email exists', async () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user);
|
||||
|
||||
// Act
|
||||
const exists = await repository.emailExists(user.email);
|
||||
|
||||
// Assert
|
||||
expect(exists).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when email does not exist', async () => {
|
||||
// Arrange
|
||||
const nonExistentEmail = AdminUser.create({
|
||||
id: 'dummy',
|
||||
email: 'non-existent@example.com',
|
||||
displayName: 'Dummy',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
}).email;
|
||||
|
||||
// Act
|
||||
const exists = await repository.emailExists(nonExistentEmail);
|
||||
|
||||
// Assert
|
||||
expect(exists).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('list', () => {
|
||||
it('should return empty list when no users exist', async () => {
|
||||
// Act
|
||||
const result = await repository.list({});
|
||||
|
||||
// Assert
|
||||
expect(result.users).toEqual([]);
|
||||
expect(result.total).toBe(0);
|
||||
expect(result.page).toBe(1);
|
||||
expect(result.limit).toBe(10);
|
||||
expect(result.totalPages).toBe(0);
|
||||
});
|
||||
|
||||
it('should return all users when no filters provided', async () => {
|
||||
// Arrange
|
||||
const user1 = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const user2 = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'User 2',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user1);
|
||||
await repository.create(user2);
|
||||
|
||||
// Act
|
||||
const result = await repository.list({});
|
||||
|
||||
// Assert
|
||||
expect(result.users).toHaveLength(2);
|
||||
expect(result.total).toBe(2);
|
||||
expect(result.page).toBe(1);
|
||||
expect(result.limit).toBe(10);
|
||||
expect(result.totalPages).toBe(1);
|
||||
});
|
||||
|
||||
it('should filter by role', async () => {
|
||||
// Arrange
|
||||
const user1 = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const admin1 = AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin1@example.com',
|
||||
displayName: 'Admin 1',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user1);
|
||||
await repository.create(admin1);
|
||||
|
||||
// Act
|
||||
const result = await repository.list({
|
||||
filter: { role: UserRole.fromString('admin') },
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result.users).toHaveLength(1);
|
||||
expect(result.users[0]?.id.value).toBe('admin-1');
|
||||
expect(result.total).toBe(1);
|
||||
});
|
||||
|
||||
it('should filter by status', async () => {
|
||||
// Arrange
|
||||
const activeUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'active@example.com',
|
||||
displayName: 'Active User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const suspendedUser = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'suspended@example.com',
|
||||
displayName: 'Suspended User',
|
||||
roles: ['user'],
|
||||
status: 'suspended',
|
||||
});
|
||||
|
||||
await repository.create(activeUser);
|
||||
await repository.create(suspendedUser);
|
||||
|
||||
// Act
|
||||
const result = await repository.list({
|
||||
filter: { status: UserStatus.fromString('suspended') },
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result.users).toHaveLength(1);
|
||||
expect(result.users[0]?.id.value).toBe('user-2');
|
||||
expect(result.total).toBe(1);
|
||||
});
|
||||
|
||||
it('should filter by email', async () => {
|
||||
// Arrange
|
||||
const user1 = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const user2 = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'other@example.com',
|
||||
displayName: 'Other User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user1);
|
||||
await repository.create(user2);
|
||||
|
||||
// Act
|
||||
const result = await repository.list({
|
||||
filter: { email: user1.email },
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result.users).toHaveLength(1);
|
||||
expect(result.users[0]?.id.value).toBe('user-1');
|
||||
expect(result.total).toBe(1);
|
||||
});
|
||||
|
||||
it('should filter by search (email or display name)', async () => {
|
||||
// Arrange
|
||||
const user1 = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'search@example.com',
|
||||
displayName: 'Search User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const user2 = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'other@example.com',
|
||||
displayName: 'Other User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user1);
|
||||
await repository.create(user2);
|
||||
|
||||
// Act
|
||||
const result = await repository.list({
|
||||
filter: { search: 'search' },
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result.users).toHaveLength(1);
|
||||
expect(result.users[0]?.id.value).toBe('user-1');
|
||||
expect(result.total).toBe(1);
|
||||
});
|
||||
|
||||
it('should apply pagination', async () => {
|
||||
// Arrange - Create 15 users
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
const user = AdminUser.create({
|
||||
id: `user-${i}`,
|
||||
email: `user${i}@example.com`,
|
||||
displayName: `User ${i}`,
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
await repository.create(user);
|
||||
}
|
||||
|
||||
// Act - Get page 2 with limit 5
|
||||
const result = await repository.list({
|
||||
pagination: { page: 2, limit: 5 },
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result.users).toHaveLength(5);
|
||||
expect(result.total).toBe(15);
|
||||
expect(result.page).toBe(2);
|
||||
expect(result.limit).toBe(5);
|
||||
expect(result.totalPages).toBe(3);
|
||||
expect(result.users[0]?.id.value).toBe('user-6');
|
||||
});
|
||||
|
||||
it('should sort by email ascending', async () => {
|
||||
// Arrange
|
||||
const user1 = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'zebra@example.com',
|
||||
displayName: 'Zebra',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const user2 = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'alpha@example.com',
|
||||
displayName: 'Alpha',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user1);
|
||||
await repository.create(user2);
|
||||
|
||||
// Act
|
||||
const result = await repository.list({
|
||||
sort: { field: 'email', direction: 'asc' },
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result.users).toHaveLength(2);
|
||||
expect(result.users[0]?.id.value).toBe('user-2');
|
||||
expect(result.users[1]?.id.value).toBe('user-1');
|
||||
});
|
||||
|
||||
it('should sort by display name descending', async () => {
|
||||
// Arrange
|
||||
const user1 = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'Zebra',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const user2 = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'Alpha',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user1);
|
||||
await repository.create(user2);
|
||||
|
||||
// Act
|
||||
const result = await repository.list({
|
||||
sort: { field: 'displayName', direction: 'desc' },
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result.users).toHaveLength(2);
|
||||
expect(result.users[0]?.id.value).toBe('user-1');
|
||||
expect(result.users[1]?.id.value).toBe('user-2');
|
||||
});
|
||||
|
||||
it('should apply multiple filters together', async () => {
|
||||
// Arrange
|
||||
const matchingUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'admin@example.com',
|
||||
displayName: 'Admin User',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const nonMatchingUser1 = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'user@example.com',
|
||||
displayName: 'User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const nonMatchingUser2 = AdminUser.create({
|
||||
id: 'user-3',
|
||||
email: 'admin-suspended@example.com',
|
||||
displayName: 'Admin User',
|
||||
roles: ['admin'],
|
||||
status: 'suspended',
|
||||
});
|
||||
|
||||
await repository.create(matchingUser);
|
||||
await repository.create(nonMatchingUser1);
|
||||
await repository.create(nonMatchingUser2);
|
||||
|
||||
// Act
|
||||
const result = await repository.list({
|
||||
filter: {
|
||||
role: UserRole.fromString('admin'),
|
||||
status: UserStatus.fromString('active'),
|
||||
search: 'admin',
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result.users).toHaveLength(1);
|
||||
expect(result.users[0]?.id.value).toBe('user-1');
|
||||
expect(result.total).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('count', () => {
|
||||
it('should return 0 when no users exist', async () => {
|
||||
// Act
|
||||
const count = await repository.count();
|
||||
|
||||
// Assert
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
|
||||
it('should return correct count of all users', async () => {
|
||||
// Arrange
|
||||
const user1 = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const user2 = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'User 2',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user1);
|
||||
await repository.create(user2);
|
||||
|
||||
// Act
|
||||
const count = await repository.count();
|
||||
|
||||
// Assert
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
|
||||
it('should count with filters', async () => {
|
||||
// Arrange
|
||||
const activeUser = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'active@example.com',
|
||||
displayName: 'Active User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const suspendedUser = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'suspended@example.com',
|
||||
displayName: 'Suspended User',
|
||||
roles: ['user'],
|
||||
status: 'suspended',
|
||||
});
|
||||
|
||||
await repository.create(activeUser);
|
||||
await repository.create(suspendedUser);
|
||||
|
||||
// Act
|
||||
const count = await repository.count({
|
||||
status: UserStatus.fromString('active'),
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update existing user', async () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user);
|
||||
|
||||
// Act
|
||||
user.updateDisplayName('Updated Name');
|
||||
const updated = await repository.update(user);
|
||||
|
||||
// Assert
|
||||
expect(updated.displayName).toBe('Updated Name');
|
||||
const found = await repository.findById(user.id);
|
||||
expect(found?.displayName).toBe('Updated Name');
|
||||
});
|
||||
|
||||
it('should throw error when updating non-existent user', async () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'non-existent',
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
await expect(repository.update(user)).rejects.toThrow('User not found');
|
||||
});
|
||||
|
||||
it('should throw error when updating with duplicate email', async () => {
|
||||
// Arrange
|
||||
const user1 = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User 1',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
const user2 = AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'User 2',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user1);
|
||||
await repository.create(user2);
|
||||
|
||||
// Act - Try to change user2's email to user1's email
|
||||
user2.updateEmail(user1.email);
|
||||
|
||||
// Assert
|
||||
await expect(repository.update(user2)).rejects.toThrow('Email already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete existing user', async () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user);
|
||||
|
||||
// Act
|
||||
await repository.delete(user.id);
|
||||
|
||||
// Assert
|
||||
const found = await repository.findById(user.id);
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw error when deleting non-existent user', async () => {
|
||||
// Arrange
|
||||
const nonExistentId = AdminUser.create({
|
||||
id: 'non-existent',
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
}).id;
|
||||
|
||||
// Act & Assert
|
||||
await expect(repository.delete(nonExistentId)).rejects.toThrow('User not found');
|
||||
});
|
||||
|
||||
it('should reduce count after deletion', async () => {
|
||||
// Arrange
|
||||
const user = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
await repository.create(user);
|
||||
const initialCount = await repository.count();
|
||||
|
||||
// Act
|
||||
await repository.delete(user.id);
|
||||
|
||||
// Assert
|
||||
const finalCount = await repository.count();
|
||||
expect(finalCount).toBe(initialCount - 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration scenarios', () => {
|
||||
it('should handle complete user lifecycle', async () => {
|
||||
// Arrange - Create user
|
||||
const user = AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'lifecycle@example.com',
|
||||
displayName: 'Lifecycle User',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
});
|
||||
|
||||
// Act - Create
|
||||
await repository.create(user);
|
||||
let found = await repository.findById(user.id);
|
||||
expect(found).toStrictEqual(user);
|
||||
|
||||
// Act - Update
|
||||
user.updateDisplayName('Updated Lifecycle');
|
||||
user.addRole(UserRole.fromString('admin'));
|
||||
await repository.update(user);
|
||||
found = await repository.findById(user.id);
|
||||
expect(found?.displayName).toBe('Updated Lifecycle');
|
||||
expect(found?.hasRole('admin')).toBe(true);
|
||||
|
||||
// Act - Delete
|
||||
await repository.delete(user.id);
|
||||
found = await repository.findById(user.id);
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle complex filtering and pagination', async () => {
|
||||
// Arrange - Create mixed users
|
||||
const users = [
|
||||
AdminUser.create({
|
||||
id: 'owner-1',
|
||||
email: 'owner@example.com',
|
||||
displayName: 'Owner User',
|
||||
roles: ['owner'],
|
||||
status: 'active',
|
||||
}),
|
||||
AdminUser.create({
|
||||
id: 'admin-1',
|
||||
email: 'admin1@example.com',
|
||||
displayName: 'Admin One',
|
||||
roles: ['admin'],
|
||||
status: 'active',
|
||||
}),
|
||||
AdminUser.create({
|
||||
id: 'admin-2',
|
||||
email: 'admin2@example.com',
|
||||
displayName: 'Admin Two',
|
||||
roles: ['admin'],
|
||||
status: 'suspended',
|
||||
}),
|
||||
AdminUser.create({
|
||||
id: 'user-1',
|
||||
email: 'user1@example.com',
|
||||
displayName: 'User One',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
}),
|
||||
AdminUser.create({
|
||||
id: 'user-2',
|
||||
email: 'user2@example.com',
|
||||
displayName: 'User Two',
|
||||
roles: ['user'],
|
||||
status: 'active',
|
||||
}),
|
||||
];
|
||||
|
||||
for (const user of users) {
|
||||
await repository.create(user);
|
||||
}
|
||||
|
||||
// Act - Get active admins, sorted by email, page 1, limit 2
|
||||
const result = await repository.list({
|
||||
filter: {
|
||||
role: UserRole.fromString('admin'),
|
||||
status: UserStatus.fromString('active'),
|
||||
},
|
||||
sort: { field: 'email', direction: 'asc' },
|
||||
pagination: { page: 1, limit: 2 },
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result.users).toHaveLength(1);
|
||||
expect(result.users[0]?.id.value).toBe('admin-1');
|
||||
expect(result.total).toBe(1);
|
||||
expect(result.totalPages).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,257 @@
|
||||
import { IAdminUserRepository, UserFilter, UserListQuery, UserListResult, StoredAdminUser } from '../../domain/repositories/IAdminUserRepository';
|
||||
import { AdminUser } from '../../domain/entities/AdminUser';
|
||||
import { UserId } from '../../domain/value-objects/UserId';
|
||||
import { Email } from '../../domain/value-objects/Email';
|
||||
|
||||
/**
|
||||
* In-memory implementation of AdminUserRepository for testing and development
|
||||
* Follows TDD - created with tests first
|
||||
*/
|
||||
export class InMemoryAdminUserRepository implements IAdminUserRepository {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Column, CreateDateColumn, Entity, Index, PrimaryColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity({ name: 'admin_users' })
|
||||
export class AdminUserOrmEntity {
|
||||
@PrimaryColumn({ type: 'uuid' })
|
||||
id!: string;
|
||||
|
||||
@Index()
|
||||
@Column({ type: 'text', unique: true })
|
||||
email!: string;
|
||||
|
||||
@Column({ type: 'text' })
|
||||
displayName!: string;
|
||||
|
||||
@Column({ type: 'jsonb' })
|
||||
roles!: string[];
|
||||
|
||||
@Column({ type: 'text' })
|
||||
status!: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
primaryDriverId?: string;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
lastLoginAt?: Date;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: Date;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export class TypeOrmAdminSchemaError extends Error {
|
||||
constructor(
|
||||
public details: {
|
||||
entityName: string;
|
||||
fieldName: string;
|
||||
reason: string;
|
||||
message: string;
|
||||
},
|
||||
) {
|
||||
super(`[TypeOrmAdminSchemaError] ${details.entityName}.${details.fieldName}: ${details.reason} - ${details.message}`);
|
||||
this.name = 'TypeOrmAdminSchemaError';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { AdminUser } from '@core/admin/domain/entities/AdminUser';
|
||||
import { AdminUserOrmEntity } from '../entities/AdminUserOrmEntity';
|
||||
import { TypeOrmAdminSchemaError } from '../errors/TypeOrmAdminSchemaError';
|
||||
import {
|
||||
assertNonEmptyString,
|
||||
assertStringArray,
|
||||
assertDate,
|
||||
assertOptionalDate,
|
||||
assertOptionalString,
|
||||
} from '../schema/TypeOrmAdminSchemaGuards';
|
||||
|
||||
export class AdminUserOrmMapper {
|
||||
toDomain(entity: AdminUserOrmEntity): AdminUser {
|
||||
const entityName = 'AdminUser';
|
||||
|
||||
try {
|
||||
assertNonEmptyString(entityName, 'id', entity.id);
|
||||
assertNonEmptyString(entityName, 'email', entity.email);
|
||||
assertNonEmptyString(entityName, 'displayName', entity.displayName);
|
||||
assertStringArray(entityName, 'roles', entity.roles);
|
||||
assertNonEmptyString(entityName, 'status', entity.status);
|
||||
assertOptionalString(entityName, 'primaryDriverId', entity.primaryDriverId);
|
||||
assertOptionalDate(entityName, 'lastLoginAt', entity.lastLoginAt);
|
||||
assertDate(entityName, 'createdAt', entity.createdAt);
|
||||
assertDate(entityName, 'updatedAt', entity.updatedAt);
|
||||
} catch (error) {
|
||||
if (error instanceof TypeOrmAdminSchemaError) {
|
||||
throw error;
|
||||
}
|
||||
const message = error instanceof Error ? error.message : 'Invalid persisted AdminUser';
|
||||
throw new TypeOrmAdminSchemaError({ entityName, fieldName: 'unknown', reason: 'invalid_shape', message });
|
||||
}
|
||||
|
||||
try {
|
||||
const domainProps: {
|
||||
id: string;
|
||||
email: string;
|
||||
displayName: string;
|
||||
roles: string[];
|
||||
status: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
primaryDriverId?: string;
|
||||
lastLoginAt?: Date;
|
||||
} = {
|
||||
id: entity.id,
|
||||
email: entity.email,
|
||||
displayName: entity.displayName,
|
||||
roles: entity.roles,
|
||||
status: entity.status,
|
||||
createdAt: entity.createdAt,
|
||||
updatedAt: entity.updatedAt,
|
||||
};
|
||||
|
||||
if (entity.primaryDriverId !== null && entity.primaryDriverId !== undefined) {
|
||||
domainProps.primaryDriverId = entity.primaryDriverId;
|
||||
}
|
||||
|
||||
if (entity.lastLoginAt !== null && entity.lastLoginAt !== undefined) {
|
||||
domainProps.lastLoginAt = entity.lastLoginAt;
|
||||
}
|
||||
|
||||
return AdminUser.create(domainProps);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Invalid persisted AdminUser';
|
||||
throw new TypeOrmAdminSchemaError({ entityName, fieldName: 'unknown', reason: 'invalid_shape', message });
|
||||
}
|
||||
}
|
||||
|
||||
toOrmEntity(adminUser: AdminUser): AdminUserOrmEntity {
|
||||
const entity = new AdminUserOrmEntity();
|
||||
|
||||
entity.id = adminUser.id.value;
|
||||
entity.email = adminUser.email.value;
|
||||
entity.displayName = adminUser.displayName;
|
||||
entity.roles = adminUser.roles.map(r => r.value);
|
||||
entity.status = adminUser.status.value;
|
||||
entity.createdAt = adminUser.createdAt;
|
||||
entity.updatedAt = adminUser.updatedAt;
|
||||
|
||||
if (adminUser.primaryDriverId !== undefined) {
|
||||
entity.primaryDriverId = adminUser.primaryDriverId;
|
||||
}
|
||||
|
||||
if (adminUser.lastLoginAt !== undefined) {
|
||||
entity.lastLoginAt = adminUser.lastLoginAt;
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
toStored(entity: AdminUserOrmEntity): AdminUser {
|
||||
return this.toDomain(entity);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,184 @@
|
||||
import type { DataSource, Repository } from 'typeorm';
|
||||
import type { IAdminUserRepository, UserListQuery, UserListResult, UserFilter, StoredAdminUser } from '@core/admin/domain/repositories/IAdminUserRepository';
|
||||
import { AdminUser } from '@core/admin/domain/entities/AdminUser';
|
||||
import { AdminUserOrmEntity } from '../entities/AdminUserOrmEntity';
|
||||
import { AdminUserOrmMapper } from '../mappers/AdminUserOrmMapper';
|
||||
import { Email } from '@core/admin/domain/value-objects/Email';
|
||||
import { UserId } from '@core/admin/domain/value-objects/UserId';
|
||||
|
||||
export class TypeOrmAdminUserRepository implements IAdminUserRepository {
|
||||
private readonly repository: Repository<AdminUserOrmEntity>;
|
||||
private readonly mapper: AdminUserOrmMapper;
|
||||
|
||||
constructor(
|
||||
dataSource: DataSource,
|
||||
mapper?: AdminUserOrmMapper,
|
||||
) {
|
||||
this.repository = dataSource.getRepository(AdminUserOrmEntity);
|
||||
this.mapper = mapper ?? new AdminUserOrmMapper();
|
||||
}
|
||||
|
||||
async save(user: AdminUser): Promise<void> {
|
||||
const entity = this.mapper.toOrmEntity(user);
|
||||
await this.repository.save(entity);
|
||||
}
|
||||
|
||||
async findById(id: UserId): Promise<AdminUser | null> {
|
||||
const entity = await this.repository.findOne({ where: { id: id.value } });
|
||||
return entity ? this.mapper.toDomain(entity) : null;
|
||||
}
|
||||
|
||||
async findByEmail(email: Email): Promise<AdminUser | null> {
|
||||
const entity = await this.repository.findOne({ where: { email: email.value } });
|
||||
return entity ? this.mapper.toDomain(entity) : null;
|
||||
}
|
||||
|
||||
async emailExists(email: Email): Promise<boolean> {
|
||||
const count = await this.repository.count({ where: { email: email.value } });
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
async existsById(id: UserId): Promise<boolean> {
|
||||
const count = await this.repository.count({ where: { id: id.value } });
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
async existsByEmail(email: Email): Promise<boolean> {
|
||||
return this.emailExists(email);
|
||||
}
|
||||
|
||||
async list(query?: UserListQuery): Promise<UserListResult> {
|
||||
const page = query?.pagination?.page ?? 1;
|
||||
const limit = query?.pagination?.limit ?? 10;
|
||||
const skip = (page - 1) * limit;
|
||||
const sortBy = query?.sort?.field ?? 'createdAt';
|
||||
const sortOrder = query?.sort?.direction ?? 'desc';
|
||||
|
||||
const where: Record<string, unknown> = {};
|
||||
|
||||
if (query?.filter?.role) {
|
||||
where.roles = { $contains: [query.filter.role.value] };
|
||||
}
|
||||
|
||||
if (query?.filter?.status) {
|
||||
where.status = query.filter.status.value;
|
||||
}
|
||||
|
||||
if (query?.filter?.search) {
|
||||
where.email = this.repository.manager.connection
|
||||
.createQueryBuilder()
|
||||
.where('email ILIKE :search', { search: `%${query.filter.search}%` })
|
||||
.orWhere('displayName ILIKE :search', { search: `%${query.filter.search}%` })
|
||||
.getQuery();
|
||||
}
|
||||
|
||||
const [entities, total] = await this.repository.findAndCount({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
order: { [sortBy]: sortOrder },
|
||||
});
|
||||
|
||||
const users = entities.map(entity => this.mapper.toDomain(entity));
|
||||
|
||||
return {
|
||||
users,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
}
|
||||
|
||||
async count(filter?: UserFilter): Promise<number> {
|
||||
const where: Record<string, unknown> = {};
|
||||
|
||||
if (filter?.role) {
|
||||
where.roles = { $contains: [filter.role.value] };
|
||||
}
|
||||
|
||||
if (filter?.status) {
|
||||
where.status = filter.status.value;
|
||||
}
|
||||
|
||||
if (filter?.search) {
|
||||
where.email = this.repository.manager.connection
|
||||
.createQueryBuilder()
|
||||
.where('email ILIKE :search', { search: `%${filter.search}%` })
|
||||
.orWhere('displayName ILIKE :search', { search: `%${filter.search}%` })
|
||||
.getQuery();
|
||||
}
|
||||
|
||||
return await this.repository.count({ where });
|
||||
}
|
||||
|
||||
async create(user: AdminUser): Promise<AdminUser> {
|
||||
const entity = this.mapper.toOrmEntity(user);
|
||||
const saved = await this.repository.save(entity);
|
||||
return this.mapper.toDomain(saved);
|
||||
}
|
||||
|
||||
async update(user: AdminUser): Promise<AdminUser> {
|
||||
const entity = this.mapper.toOrmEntity(user);
|
||||
const updated = await this.repository.save(entity);
|
||||
return this.mapper.toDomain(updated);
|
||||
}
|
||||
|
||||
async delete(id: UserId): Promise<void> {
|
||||
await this.repository.delete({ id: 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { TypeOrmAdminSchemaError } from '../errors/TypeOrmAdminSchemaError';
|
||||
|
||||
export function assertNonEmptyString(entityName: string, fieldName: string, value: unknown): void {
|
||||
if (typeof value !== 'string' || value.trim().length === 0) {
|
||||
throw new TypeOrmAdminSchemaError({
|
||||
entityName,
|
||||
fieldName,
|
||||
reason: 'INVALID_STRING',
|
||||
message: `Field ${fieldName} must be a non-empty string`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function assertStringArray(entityName: string, fieldName: string, value: unknown): void {
|
||||
if (!Array.isArray(value) || !value.every(item => typeof item === 'string')) {
|
||||
throw new TypeOrmAdminSchemaError({
|
||||
entityName,
|
||||
fieldName,
|
||||
reason: 'INVALID_STRING_ARRAY',
|
||||
message: `Field ${fieldName} must be an array of strings`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function assertDate(entityName: string, fieldName: string, value: unknown): void {
|
||||
if (!(value instanceof Date) || isNaN(value.getTime())) {
|
||||
throw new TypeOrmAdminSchemaError({
|
||||
entityName,
|
||||
fieldName,
|
||||
reason: 'INVALID_DATE',
|
||||
message: `Field ${fieldName} must be a valid Date`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function assertOptionalDate(entityName: string, fieldName: string, value: unknown): void {
|
||||
if (value === null || value === undefined) {
|
||||
return;
|
||||
}
|
||||
assertDate(entityName, fieldName, value);
|
||||
}
|
||||
|
||||
export function assertOptionalString(entityName: string, fieldName: string, value: unknown): void {
|
||||
if (value === null || value === undefined) {
|
||||
return;
|
||||
}
|
||||
if (typeof value !== 'string') {
|
||||
throw new TypeOrmAdminSchemaError({
|
||||
entityName,
|
||||
fieldName,
|
||||
reason: 'INVALID_OPTIONAL_STRING',
|
||||
message: `Field ${fieldName} must be a string or undefined`,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user