refactor use cases

This commit is contained in:
2025-12-21 01:20:27 +01:00
parent c12656d671
commit 8ecd638396
39 changed files with 2523 additions and 686 deletions

View File

@@ -1,56 +1,112 @@
/**
* Login with Email Use Case
*
*
* Authenticates a user with email and password.
*/
import type { IUserRepository } from '../../domain/repositories/IUserRepository';
import type { AuthenticatedUserDTO } from '../dto/AuthenticatedUserDTO';
import type { AuthSessionDTO } from '../dto/AuthSessionDTO';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { UseCaseOutputPort, Logger } from '@core/shared/application';
export interface LoginCommandDTO {
export type LoginWithEmailInput = {
email: string;
password: string;
}
};
export type LoginWithEmailResult = {
sessionToken: string;
userId: string;
displayName: string;
email?: string;
primaryDriverId?: string;
issuedAt: number;
expiresAt: number;
};
export type LoginWithEmailErrorCode =
| 'INVALID_INPUT'
| 'INVALID_CREDENTIALS'
| 'REPOSITORY_ERROR';
export type LoginWithEmailApplicationError = ApplicationErrorCode<
LoginWithEmailErrorCode,
{ message: string }
>;
export class LoginWithEmailUseCase {
constructor(
private readonly userRepository: IUserRepository,
private readonly sessionPort: IdentitySessionPort,
private readonly logger: Logger,
private readonly output: UseCaseOutputPort<LoginWithEmailResult>,
) {}
async execute(command: LoginCommandDTO): Promise<AuthSessionDTO> {
// Validate inputs
if (!command.email || !command.password) {
throw new Error('Email and password are required');
async execute(input: LoginWithEmailInput): Promise<Result<void, LoginWithEmailApplicationError>> {
try {
if (!input.email || !input.password) {
return Result.err({
code: 'INVALID_INPUT',
details: { message: 'Email and password are required' },
} as LoginWithEmailApplicationError);
}
const normalizedEmail = input.email.toLowerCase().trim();
const user = await this.userRepository.findByEmail(normalizedEmail);
if (!user) {
return Result.err({
code: 'INVALID_CREDENTIALS',
details: { message: 'Invalid email or password' },
} as LoginWithEmailApplicationError);
}
const passwordHash = await this.hashPassword(input.password, user.salt);
if (passwordHash !== user.passwordHash) {
return Result.err({
code: 'INVALID_CREDENTIALS',
details: { message: 'Invalid email or password' },
} as LoginWithEmailApplicationError);
}
const session = await this.sessionPort.createSession({
id: user.id,
displayName: user.displayName,
email: user.email,
primaryDriverId: user.primaryDriverId,
} as any);
const result: LoginWithEmailResult = {
sessionToken: (session as any).token,
userId: (session as any).user.id,
displayName: (session as any).user.displayName,
email: (session as any).user.email,
primaryDriverId: (session as any).user.primaryDriverId,
issuedAt: (session as any).issuedAt,
expiresAt: (session as any).expiresAt,
};
this.output.present(result);
return Result.ok(undefined);
} catch (error) {
const message =
error instanceof Error && error.message
? error.message
: 'Failed to execute LoginWithEmailUseCase';
this.logger.error(
'LoginWithEmailUseCase.execute failed',
error instanceof Error ? error : undefined,
{ input },
);
return Result.err({
code: 'REPOSITORY_ERROR',
details: { message },
} as LoginWithEmailApplicationError);
}
// Find user by email
const user = await this.userRepository.findByEmail(command.email.toLowerCase().trim());
if (!user) {
throw new Error('Invalid email or password');
}
// Verify password
const passwordHash = await this.hashPassword(command.password, user.salt);
if (passwordHash !== user.passwordHash) {
throw new Error('Invalid email or password');
}
// Create session
const authenticatedUserBase: AuthenticatedUserDTO = {
id: user.id,
displayName: user.displayName,
email: user.email,
};
const authenticatedUser: AuthenticatedUserDTO =
user.primaryDriverId !== undefined
? { ...authenticatedUserBase, primaryDriverId: user.primaryDriverId }
: authenticatedUserBase;
return this.sessionPort.createSession(authenticatedUser);
}
private async hashPassword(password: string, salt: string): Promise<string> {