presenter refactoring
This commit is contained in:
@@ -8,22 +8,26 @@ export class AuthController {
|
||||
|
||||
@Post('signup')
|
||||
async signup(@Body() params: SignupParams): Promise<AuthSessionDTO> {
|
||||
return this.authService.signupWithEmail(params);
|
||||
const presenter = await this.authService.signupWithEmail(params);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Post('login')
|
||||
async login(@Body() params: LoginParams): Promise<AuthSessionDTO> {
|
||||
return this.authService.loginWithEmail(params);
|
||||
const presenter = await this.authService.loginWithEmail(params);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Get('session')
|
||||
async getSession(): Promise<AuthSessionDTO | null> {
|
||||
return this.authService.getCurrentSession();
|
||||
const presenter = await this.authService.getCurrentSession();
|
||||
return presenter ? presenter.viewModel : null;
|
||||
}
|
||||
|
||||
@Post('logout')
|
||||
async logout(): Promise<void> {
|
||||
return this.authService.logout();
|
||||
async logout(): Promise<{ success: boolean }> {
|
||||
const presenter = await this.authService.logout();
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import type { IPasswordHashingService } from '@core/identity/domain/services/Pas
|
||||
import type { Logger } from "@core/shared/application";
|
||||
import { AUTH_REPOSITORY_TOKEN, IDENTITY_SESSION_PORT_TOKEN, LOGGER_TOKEN, PASSWORD_HASHING_SERVICE_TOKEN, USER_REPOSITORY_TOKEN } from './AuthProviders';
|
||||
import { AuthSessionDTO, LoginParams, SignupParams, AuthenticatedUserDTO } from './dtos/AuthDto';
|
||||
import { AuthSessionPresenter } from './presenters/AuthSessionPresenter';
|
||||
import { CommandResultPresenter } from './presenters/CommandResultPresenter';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
@@ -50,7 +52,7 @@ export class AuthService {
|
||||
};
|
||||
}
|
||||
|
||||
async getCurrentSession(): Promise<AuthSessionDTO | null> {
|
||||
async getCurrentSession(): Promise<AuthSessionPresenter | null> {
|
||||
this.logger.debug('[AuthService] Attempting to get current session.');
|
||||
const coreSession = await this.identitySessionPort.getCurrentSession();
|
||||
if (!coreSession) {
|
||||
@@ -59,36 +61,34 @@ export class AuthService {
|
||||
|
||||
const user = await this.userRepository.findById(coreSession.user.id); // Use userRepository to fetch full user
|
||||
if (!user) {
|
||||
// If session exists but user doesn't in DB, perhaps clear session?
|
||||
this.logger.warn(`[AuthService] Session found for user ID ${coreSession.user.id}, but user not found in repository.`);
|
||||
await this.identitySessionPort.clearSession(); // Clear potentially stale session
|
||||
return null;
|
||||
// If session exists but user doesn't in DB, perhaps clear session?
|
||||
this.logger.warn(`[AuthService] Session found for user ID ${coreSession.user.id}, but user not found in repository.`);
|
||||
await this.identitySessionPort.clearSession(); // Clear potentially stale session
|
||||
return null;
|
||||
}
|
||||
|
||||
const authenticatedUserDTO = this.mapUserToAuthenticatedUserDTO(User.fromStored(user));
|
||||
|
||||
return {
|
||||
token: coreSession.token,
|
||||
user: authenticatedUserDTO,
|
||||
};
|
||||
const presenter = new AuthSessionPresenter();
|
||||
presenter.present({ token: coreSession.token, user: authenticatedUserDTO });
|
||||
return presenter;
|
||||
}
|
||||
|
||||
async signupWithEmail(params: SignupParams): Promise<AuthSessionDTO> {
|
||||
async signupWithEmail(params: SignupParams): Promise<AuthSessionPresenter> {
|
||||
this.logger.debug(`[AuthService] Attempting signup for email: ${params.email}`);
|
||||
const user = await this.signupUseCase.execute(params.email, params.password, params.displayName);
|
||||
|
||||
|
||||
// Create session after successful signup
|
||||
const authenticatedUserDTO = this.mapUserToAuthenticatedUserDTO(user);
|
||||
const coreDto = this.mapToCoreAuthenticatedUserDTO(authenticatedUserDTO);
|
||||
const session = await this.identitySessionPort.createSession(coreDto);
|
||||
|
||||
return {
|
||||
token: session.token,
|
||||
user: authenticatedUserDTO,
|
||||
};
|
||||
const presenter = new AuthSessionPresenter();
|
||||
presenter.present({ token: session.token, user: authenticatedUserDTO });
|
||||
return presenter;
|
||||
}
|
||||
|
||||
async loginWithEmail(params: LoginParams): Promise<AuthSessionDTO> {
|
||||
async loginWithEmail(params: LoginParams): Promise<AuthSessionPresenter> {
|
||||
this.logger.debug(`[AuthService] Attempting login for email: ${params.email}`);
|
||||
try {
|
||||
const user = await this.loginUseCase.execute(params.email, params.password);
|
||||
@@ -97,10 +97,9 @@ export class AuthService {
|
||||
const coreDto = this.mapToCoreAuthenticatedUserDTO(authenticatedUserDTO);
|
||||
const session = await this.identitySessionPort.createSession(coreDto);
|
||||
|
||||
return {
|
||||
token: session.token,
|
||||
user: authenticatedUserDTO,
|
||||
};
|
||||
const presenter = new AuthSessionPresenter();
|
||||
presenter.present({ token: session.token, user: authenticatedUserDTO });
|
||||
return presenter;
|
||||
} catch (error) {
|
||||
this.logger.error(`[AuthService] Login failed for email ${params.email}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw new InternalServerErrorException('Login failed due to invalid credentials or server error.');
|
||||
@@ -108,8 +107,11 @@ export class AuthService {
|
||||
}
|
||||
|
||||
|
||||
async logout(): Promise<void> {
|
||||
async logout(): Promise<CommandResultPresenter> {
|
||||
this.logger.debug('[AuthService] Attempting logout.');
|
||||
const presenter = new CommandResultPresenter();
|
||||
await this.logoutUseCase.execute();
|
||||
presenter.present({ success: true });
|
||||
return presenter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { AuthSessionPresenter } from './AuthSessionPresenter';
|
||||
import { AuthenticatedUserDTO } from '../dtos/AuthDto';
|
||||
|
||||
describe('AuthSessionPresenter', () => {
|
||||
let presenter: AuthSessionPresenter;
|
||||
|
||||
beforeEach(() => {
|
||||
presenter = new AuthSessionPresenter();
|
||||
});
|
||||
|
||||
it('maps token and user DTO correctly', () => {
|
||||
const user: AuthenticatedUserDTO = {
|
||||
userId: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'Test User',
|
||||
};
|
||||
|
||||
presenter.present({ token: 'token-123', user });
|
||||
|
||||
expect(presenter.viewModel).toEqual({
|
||||
token: 'token-123',
|
||||
user: {
|
||||
userId: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'Test User',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('reset clears state and causes viewModel to throw', () => {
|
||||
const user: AuthenticatedUserDTO = {
|
||||
userId: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'Test User',
|
||||
};
|
||||
|
||||
presenter.present({ token: 'token-123', user });
|
||||
expect(presenter.viewModel).toBeDefined();
|
||||
|
||||
presenter.reset();
|
||||
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
});
|
||||
|
||||
it('getViewModel returns null when not presented', () => {
|
||||
expect(presenter.getViewModel()).toBeNull();
|
||||
});
|
||||
|
||||
it('getViewModel returns the same DTO after present', () => {
|
||||
const user: AuthenticatedUserDTO = {
|
||||
userId: 'user-1',
|
||||
email: 'user@example.com',
|
||||
displayName: 'Test User',
|
||||
};
|
||||
|
||||
presenter.present({ token: 'token-123', user });
|
||||
|
||||
expect(presenter.getViewModel()).toEqual(presenter.viewModel);
|
||||
});
|
||||
});
|
||||
31
apps/api/src/domain/auth/presenters/AuthSessionPresenter.ts
Normal file
31
apps/api/src/domain/auth/presenters/AuthSessionPresenter.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { AuthSessionDTO, AuthenticatedUserDTO } from '../dtos/AuthDto';
|
||||
|
||||
export interface AuthSessionViewModel extends AuthSessionDTO {}
|
||||
|
||||
export class AuthSessionPresenter {
|
||||
private result: AuthSessionViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(input: { token: string; user: AuthenticatedUserDTO }): void {
|
||||
this.result = {
|
||||
token: input.token,
|
||||
user: {
|
||||
userId: input.user.userId,
|
||||
email: input.user.email,
|
||||
displayName: input.user.displayName,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): AuthSessionViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
|
||||
getViewModel(): AuthSessionViewModel | null {
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user