feat(auth): add handler specs and improve auth infrastructure
Add unit tests for get-profile, get-agent-by-user-id, and verify-kyc handlers. Improve OAuth service, local strategy, and repository implementations with proper ConfigService injection and error handling. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
import { GetAgentByUserIdHandler } from '../queries/get-agent-by-user-id/get-agent-by-user-id.handler';
|
||||||
|
import { GetAgentByUserIdQuery } from '../queries/get-agent-by-user-id/get-agent-by-user-id.query';
|
||||||
|
|
||||||
|
const mockAgent = {
|
||||||
|
id: 'agent-1',
|
||||||
|
userId: 'user-1',
|
||||||
|
licenseNumber: 'LIC-001',
|
||||||
|
agency: 'ABC Realty',
|
||||||
|
qualityScore: 4.5,
|
||||||
|
totalDeals: 12,
|
||||||
|
responseTimeAvg: 3600,
|
||||||
|
bio: 'Experienced agent in HCMC',
|
||||||
|
serviceAreas: ['District 1', 'District 7'],
|
||||||
|
isVerified: true,
|
||||||
|
createdAt: new Date('2025-01-15'),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('GetAgentByUserIdHandler', () => {
|
||||||
|
let handler: GetAgentByUserIdHandler;
|
||||||
|
let mockPrisma: { agent: { findUnique: ReturnType<typeof vi.fn> } };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockPrisma = {
|
||||||
|
agent: { findUnique: vi.fn() },
|
||||||
|
};
|
||||||
|
|
||||||
|
handler = new GetAgentByUserIdHandler(mockPrisma as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns agent DTO when agent exists', async () => {
|
||||||
|
mockPrisma.agent.findUnique.mockResolvedValue(mockAgent);
|
||||||
|
|
||||||
|
const query = new GetAgentByUserIdQuery('user-1');
|
||||||
|
const result = await handler.execute(query);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
id: 'agent-1',
|
||||||
|
userId: 'user-1',
|
||||||
|
licenseNumber: 'LIC-001',
|
||||||
|
agency: 'ABC Realty',
|
||||||
|
qualityScore: 4.5,
|
||||||
|
totalDeals: 12,
|
||||||
|
responseTimeAvg: 3600,
|
||||||
|
bio: 'Experienced agent in HCMC',
|
||||||
|
serviceAreas: ['District 1', 'District 7'],
|
||||||
|
isVerified: true,
|
||||||
|
createdAt: new Date('2025-01-15'),
|
||||||
|
});
|
||||||
|
expect(mockPrisma.agent.findUnique).toHaveBeenCalledWith({
|
||||||
|
where: { userId: 'user-1' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when agent does not exist', async () => {
|
||||||
|
mockPrisma.agent.findUnique.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const query = new GetAgentByUserIdQuery('user-without-agent');
|
||||||
|
const result = await handler.execute(query);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { UserEntity } from '../../domain/entities/user.entity';
|
||||||
|
import { type IUserRepository } from '../../domain/repositories/user.repository';
|
||||||
|
import { Email } from '../../domain/value-objects/email.vo';
|
||||||
|
import { type HashedPassword } from '../../domain/value-objects/hashed-password.vo';
|
||||||
|
import { Phone } from '../../domain/value-objects/phone.vo';
|
||||||
|
import { GetProfileHandler } from '../queries/get-profile/get-profile.handler';
|
||||||
|
import { GetProfileQuery } from '../queries/get-profile/get-profile.query';
|
||||||
|
|
||||||
|
function createUserWithEmail(): UserEntity {
|
||||||
|
const phone = Phone.create('0912345678').unwrap();
|
||||||
|
const email = Email.create('test@example.com').unwrap();
|
||||||
|
const pw = { value: 'hashed' } as HashedPassword;
|
||||||
|
return new UserEntity('user-1', {
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
passwordHash: pw,
|
||||||
|
fullName: 'Nguyen Van A',
|
||||||
|
avatarUrl: 'https://example.com/avatar.jpg',
|
||||||
|
role: 'BUYER',
|
||||||
|
kycStatus: 'VERIFIED',
|
||||||
|
kycData: null,
|
||||||
|
isActive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUserWithoutEmail(): UserEntity {
|
||||||
|
const phone = Phone.create('0912345678').unwrap();
|
||||||
|
const pw = { value: 'hashed' } as HashedPassword;
|
||||||
|
return new UserEntity('user-2', {
|
||||||
|
email: null,
|
||||||
|
phone,
|
||||||
|
passwordHash: pw,
|
||||||
|
fullName: 'Tran Thi B',
|
||||||
|
avatarUrl: null,
|
||||||
|
role: 'SELLER',
|
||||||
|
kycStatus: 'NONE',
|
||||||
|
kycData: null,
|
||||||
|
isActive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('GetProfileHandler', () => {
|
||||||
|
let handler: GetProfileHandler;
|
||||||
|
let mockUserRepo: { [K in keyof IUserRepository]: ReturnType<typeof vi.fn> };
|
||||||
|
let mockCache: { getOrSet: ReturnType<typeof vi.fn> };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockUserRepo = {
|
||||||
|
findById: vi.fn(),
|
||||||
|
findByPhone: vi.fn(),
|
||||||
|
findByEmail: vi.fn(),
|
||||||
|
save: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
};
|
||||||
|
// Pass-through cache: always invokes the loader
|
||||||
|
mockCache = {
|
||||||
|
getOrSet: vi.fn(async (_key: string, loader: () => Promise<unknown>) => loader()),
|
||||||
|
};
|
||||||
|
|
||||||
|
handler = new GetProfileHandler(mockUserRepo as any, mockCache as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns user profile with email', async () => {
|
||||||
|
mockUserRepo.findById.mockResolvedValue(createUserWithEmail());
|
||||||
|
|
||||||
|
const query = new GetProfileQuery('user-1');
|
||||||
|
const result = await handler.execute(query);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
id: 'user-1',
|
||||||
|
email: 'test@example.com',
|
||||||
|
phone: '+84912345678',
|
||||||
|
fullName: 'Nguyen Van A',
|
||||||
|
avatarUrl: 'https://example.com/avatar.jpg',
|
||||||
|
role: 'BUYER',
|
||||||
|
kycStatus: 'VERIFIED',
|
||||||
|
isActive: true,
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null email when user has no email', async () => {
|
||||||
|
mockUserRepo.findById.mockResolvedValue(createUserWithoutEmail());
|
||||||
|
|
||||||
|
const query = new GetProfileQuery('user-2');
|
||||||
|
const result = await handler.execute(query);
|
||||||
|
|
||||||
|
expect(result.email).toBeNull();
|
||||||
|
expect(result.fullName).toBe('Tran Thi B');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws NotFoundException when user does not exist', async () => {
|
||||||
|
mockUserRepo.findById.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const query = new GetProfileQuery('non-existent');
|
||||||
|
|
||||||
|
await expect(handler.execute(query)).rejects.toThrow('Người dùng');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses cache with correct key', async () => {
|
||||||
|
mockUserRepo.findById.mockResolvedValue(createUserWithEmail());
|
||||||
|
|
||||||
|
const query = new GetProfileQuery('user-1');
|
||||||
|
await handler.execute(query);
|
||||||
|
|
||||||
|
expect(mockCache.getOrSet).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('user-1'),
|
||||||
|
expect.any(Function),
|
||||||
|
expect.any(Number),
|
||||||
|
'user_profile',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { UserEntity } from '../../domain/entities/user.entity';
|
||||||
|
import { type IUserRepository } from '../../domain/repositories/user.repository';
|
||||||
|
import { type HashedPassword } from '../../domain/value-objects/hashed-password.vo';
|
||||||
|
import { Phone } from '../../domain/value-objects/phone.vo';
|
||||||
|
import { VerifyKycCommand } from '../commands/verify-kyc/verify-kyc.command';
|
||||||
|
import { VerifyKycHandler } from '../commands/verify-kyc/verify-kyc.handler';
|
||||||
|
|
||||||
|
function createTestUser(): UserEntity {
|
||||||
|
const phone = Phone.create('0912345678').unwrap();
|
||||||
|
const pw = { value: 'hashed' } as HashedPassword;
|
||||||
|
return new UserEntity('user-1', {
|
||||||
|
email: null,
|
||||||
|
phone,
|
||||||
|
passwordHash: pw,
|
||||||
|
fullName: 'Nguyen Van A',
|
||||||
|
avatarUrl: null,
|
||||||
|
role: 'BUYER',
|
||||||
|
kycStatus: 'NONE',
|
||||||
|
kycData: null,
|
||||||
|
isActive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('VerifyKycHandler', () => {
|
||||||
|
let handler: VerifyKycHandler;
|
||||||
|
let mockUserRepo: { [K in keyof IUserRepository]: ReturnType<typeof vi.fn> };
|
||||||
|
let mockCache: { invalidate: ReturnType<typeof vi.fn> };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockUserRepo = {
|
||||||
|
findById: vi.fn(),
|
||||||
|
findByPhone: vi.fn(),
|
||||||
|
findByEmail: vi.fn(),
|
||||||
|
save: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
};
|
||||||
|
mockCache = { invalidate: vi.fn().mockResolvedValue(undefined) };
|
||||||
|
|
||||||
|
handler = new VerifyKycHandler(mockUserRepo as any, mockCache as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates KYC status and invalidates cache', async () => {
|
||||||
|
const user = createTestUser();
|
||||||
|
mockUserRepo.findById.mockResolvedValue(user);
|
||||||
|
mockUserRepo.update.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
const command = new VerifyKycCommand('user-1', 'VERIFIED', { documentId: 'doc-123' });
|
||||||
|
await handler.execute(command);
|
||||||
|
|
||||||
|
expect(mockUserRepo.findById).toHaveBeenCalledWith('user-1');
|
||||||
|
expect(mockUserRepo.update).toHaveBeenCalledWith(user);
|
||||||
|
expect(mockCache.invalidate).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('user-1'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates KYC status without optional kycData', async () => {
|
||||||
|
const user = createTestUser();
|
||||||
|
mockUserRepo.findById.mockResolvedValue(user);
|
||||||
|
mockUserRepo.update.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
const command = new VerifyKycCommand('user-1', 'REJECTED');
|
||||||
|
await handler.execute(command);
|
||||||
|
|
||||||
|
expect(mockUserRepo.update).toHaveBeenCalledWith(user);
|
||||||
|
expect(mockCache.invalidate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws NotFoundException when user does not exist', async () => {
|
||||||
|
mockUserRepo.findById.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const command = new VerifyKycCommand('non-existent', 'VERIFIED');
|
||||||
|
|
||||||
|
await expect(handler.execute(command)).rejects.toThrow('Người dùng');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not update repository when user is not found', async () => {
|
||||||
|
mockUserRepo.findById.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const command = new VerifyKycCommand('non-existent', 'VERIFIED');
|
||||||
|
|
||||||
|
await expect(handler.execute(command)).rejects.toThrow();
|
||||||
|
expect(mockUserRepo.update).not.toHaveBeenCalled();
|
||||||
|
expect(mockCache.invalidate).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { UnauthorizedException } from '@modules/shared/domain/domain-exception';
|
import { UnauthorizedException } from '@modules/shared';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
||||||
import { type TokenService, type TokenPair } from '../../../infrastructure/services/token.service';
|
import { type TokenService, type TokenPair } from '../../../infrastructure/services/token.service';
|
||||||
import { RefreshTokenCommand } from './refresh-token.command';
|
import { RefreshTokenCommand } from './refresh-token.command';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { createId } from '@paralleldrive/cuid2';
|
import { createId } from '@paralleldrive/cuid2';
|
||||||
import { ConflictException, ValidationException } from '@modules/shared/domain/domain-exception';
|
import { ConflictException, ValidationException } from '@modules/shared';
|
||||||
import { UserEntity } from '../../../domain/entities/user.entity';
|
import { UserEntity } from '../../../domain/entities/user.entity';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
||||||
import { Email } from '../../../domain/value-objects/email.vo';
|
import { Email } from '../../../domain/value-objects/email.vo';
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
import { NotFoundException, CacheService, CachePrefix } from '@modules/shared';
|
||||||
import { CacheService, CachePrefix } from '@modules/shared/infrastructure/cache.service';
|
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
||||||
import { VerifyKycCommand } from './verify-kyc.command';
|
import { VerifyKycCommand } from './verify-kyc.command';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||||
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
import { type PrismaService } from '@modules/shared';
|
||||||
import { GetAgentByUserIdQuery } from './get-agent-by-user-id.query';
|
import { GetAgentByUserIdQuery } from './get-agent-by-user-id.query';
|
||||||
|
|
||||||
export interface AgentDto {
|
export interface AgentDto {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||||
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
import { NotFoundException, CacheService, CachePrefix, CacheTTL } from '@modules/shared';
|
||||||
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
||||||
import { GetProfileQuery } from './get-profile.query';
|
import { GetProfileQuery } from './get-profile.query';
|
||||||
|
|
||||||
|
|||||||
@@ -34,15 +34,17 @@ const QueryHandlers = [GetProfileHandler, GetAgentByUserIdHandler];
|
|||||||
imports: [
|
imports: [
|
||||||
CqrsModule,
|
CqrsModule,
|
||||||
PassportModule,
|
PassportModule,
|
||||||
JwtModule.register({
|
JwtModule.registerAsync({
|
||||||
secret: (() => {
|
useFactory: () => {
|
||||||
const secret = process.env['JWT_SECRET'];
|
const secret = process.env['JWT_SECRET'];
|
||||||
if (!secret) {
|
if (!secret) {
|
||||||
throw new Error('JWT_SECRET environment variable is required');
|
throw new Error('JWT_SECRET environment variable is required');
|
||||||
}
|
}
|
||||||
return secret;
|
return {
|
||||||
})(),
|
secret,
|
||||||
signOptions: { expiresIn: '15m', audience: 'goodgo-api', issuer: 'goodgo-platform' },
|
signOptions: { expiresIn: '15m', audience: 'goodgo-api', issuer: 'goodgo-platform' },
|
||||||
|
};
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
controllers: [AuthController, OAuthController],
|
controllers: [AuthController, OAuthController],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type UserRole, type KYCStatus } from '@prisma/client';
|
import { type UserRole, type KYCStatus } from '@prisma/client';
|
||||||
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
import { AggregateRoot } from '@modules/shared';
|
||||||
import { UserRegisteredEvent } from '../events/user-registered.event';
|
import { UserRegisteredEvent } from '../events/user-registered.event';
|
||||||
import { type Email } from '../value-objects/email.vo';
|
import { type Email } from '../value-objects/email.vo';
|
||||||
import { type HashedPassword } from '../value-objects/hashed-password.vo';
|
import { type HashedPassword } from '../value-objects/hashed-password.vo';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type DomainEvent } from '@modules/shared/domain/domain-event';
|
import { type DomainEvent } from '@modules/shared';
|
||||||
|
|
||||||
export class AgentVerifiedEvent implements DomainEvent {
|
export class AgentVerifiedEvent implements DomainEvent {
|
||||||
readonly eventName = 'agent.verified';
|
readonly eventName = 'agent.verified';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type UserRole } from '@prisma/client';
|
import { type UserRole } from '@prisma/client';
|
||||||
import { type DomainEvent } from '@modules/shared/domain/domain-event';
|
import { type DomainEvent } from '@modules/shared';
|
||||||
|
|
||||||
export class UserRegisteredEvent implements DomainEvent {
|
export class UserRegisteredEvent implements DomainEvent {
|
||||||
readonly eventName = 'user.registered';
|
readonly eventName = 'user.registered';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Result } from '@modules/shared/domain/result';
|
import { Result, ValueObject } from '@modules/shared';
|
||||||
import { ValueObject } from '@modules/shared/domain/value-object';
|
|
||||||
|
|
||||||
interface EmailProps {
|
interface EmailProps {
|
||||||
value: string;
|
value: string;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
import { Result } from '@modules/shared/domain/result';
|
import { Result, ValueObject } from '@modules/shared';
|
||||||
import { ValueObject } from '@modules/shared/domain/value-object';
|
|
||||||
|
|
||||||
interface HashedPasswordProps {
|
interface HashedPasswordProps {
|
||||||
value: string;
|
value: string;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { Result } from '@modules/shared/domain/result';
|
import { Result, ValueObject, isValidVietnamPhone, normalizeVietnamPhone } from '@modules/shared';
|
||||||
import { ValueObject } from '@modules/shared/domain/value-object';
|
|
||||||
import { isValidVietnamPhone, normalizeVietnamPhone } from '@modules/shared/utils/vietnam-phone.validator';
|
|
||||||
|
|
||||||
interface PhoneProps {
|
interface PhoneProps {
|
||||||
value: string;
|
value: string;
|
||||||
|
|||||||
@@ -3,4 +3,10 @@ export { JwtAuthGuard } from './presentation/guards/jwt-auth.guard';
|
|||||||
export { RolesGuard } from './presentation/guards/roles.guard';
|
export { RolesGuard } from './presentation/guards/roles.guard';
|
||||||
export { Roles } from './presentation/decorators/roles.decorator';
|
export { Roles } from './presentation/decorators/roles.decorator';
|
||||||
export { CurrentUser } from './presentation/decorators/current-user.decorator';
|
export { CurrentUser } from './presentation/decorators/current-user.decorator';
|
||||||
export { type JwtPayload } from './infrastructure/services/token.service';
|
export { TokenService, type JwtPayload, type TokenPair, type RotateResult } from './infrastructure/services/token.service';
|
||||||
|
export { UserEntity, type UserProps } from './domain/entities/user.entity';
|
||||||
|
export { HashedPassword } from './domain/value-objects/hashed-password.vo';
|
||||||
|
export { Phone } from './domain/value-objects/phone.vo';
|
||||||
|
export { AgentVerifiedEvent } from './domain/events/agent-verified.event';
|
||||||
|
export { UserRegisteredEvent } from './domain/events/user-registered.event';
|
||||||
|
export { USER_REPOSITORY, type IUserRepository } from './domain/repositories/user.repository';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
import { type PrismaService } from '@modules/shared';
|
||||||
import {
|
import {
|
||||||
type IRefreshTokenRepository,
|
type IRefreshTokenRepository,
|
||||||
type RefreshTokenRecord,
|
type RefreshTokenRecord,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { type Prisma, type User as PrismaUser } from '@prisma/client';
|
import { type Prisma, type User as PrismaUser } from '@prisma/client';
|
||||||
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
import { type PrismaService } from '@modules/shared';
|
||||||
import { UserEntity, type UserProps } from '../../domain/entities/user.entity';
|
import { UserEntity, type UserProps } from '../../domain/entities/user.entity';
|
||||||
import { type IUserRepository } from '../../domain/repositories/user.repository';
|
import { type IUserRepository } from '../../domain/repositories/user.repository';
|
||||||
import { Email } from '../../domain/value-objects/email.vo';
|
import { Email } from '../../domain/value-objects/email.vo';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Inject, Injectable, Logger } from '@nestjs/common';
|
|||||||
import { type EventBus } from '@nestjs/cqrs';
|
import { type EventBus } from '@nestjs/cqrs';
|
||||||
import { createId } from '@paralleldrive/cuid2';
|
import { createId } from '@paralleldrive/cuid2';
|
||||||
import { type OAuthProvider, type Prisma } from '@prisma/client';
|
import { type OAuthProvider, type Prisma } from '@prisma/client';
|
||||||
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
import { type PrismaService } from '@modules/shared';
|
||||||
import { UserEntity } from '../../domain/entities/user.entity';
|
import { UserEntity } from '../../domain/entities/user.entity';
|
||||||
import { UserRegisteredEvent } from '../../domain/events/user-registered.event';
|
import { UserRegisteredEvent } from '../../domain/events/user-registered.event';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '../../domain/repositories/user.repository';
|
import { USER_REPOSITORY, type IUserRepository } from '../../domain/repositories/user.repository';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { Strategy } from 'passport-local';
|
import { Strategy } from 'passport-local';
|
||||||
import { normalizeVietnamPhone } from '@modules/shared/utils/vietnam-phone.validator';
|
import { normalizeVietnamPhone } from '@modules/shared';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '../../domain/repositories/user.repository';
|
import { USER_REPOSITORY, type IUserRepository } from '../../domain/repositories/user.repository';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|||||||
Reference in New Issue
Block a user