refactor use cases
This commit is contained in:
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user