diff --git a/src/app.module.ts b/src/app.module.ts index e95e8e9..f753660 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { UsersModule } from './users/users.module'; +import { UsersModule } from './presentation/user/users.module'; import { CommonModule } from './common/common.module'; import { AuthModule } from './auth/auth.module'; import { MailModule } from './mail/mail.module'; @@ -15,7 +15,7 @@ import { TypeOrmConfigService } from './database/typerom-config.service'; import { OpenaiModule } from './openai/openai.module'; import { AppController } from './app.controller'; import { AopModule } from './common/aop/aop.module'; -import { InfraModule } from './infra/infra.module'; +import { InfraModule } from './infrastructure/infra.module'; @Module({ imports: [ diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 2ab07d8..e0d3dbb 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -10,7 +10,7 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; import { ErrorOutput } from '../common/dtos/output.dto'; -import { User } from '../users/entities/user.entity'; +import { User } from '../domain/user/entities/user.entity'; import { AuthUser } from './auth-user.decorator'; import { AuthService } from './auth.service'; import { diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 2cfd8fb..40e5f79 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -8,12 +8,13 @@ import { JwtStrategy } from './jwt/jwt.strategy'; import { GoogleStrategy } from './passport/google/google.strategy'; import { customJwtService } from './jwt/jwt.service'; import { TWOHOUR } from './jwt/jwt.payload'; -import { UsersModule } from '../users/users.module'; +import { UsersModule } from '../presentation/user/users.module'; import { OAuthUtil } from './util/oauth.util'; import { ContentsModule } from '../contents/contents.module'; import { OAuthService } from './oauth.service'; import { CategoryModule } from '../categories/category.module'; -import { RedisModule } from '../infra/redis/redis.module'; +import { RedisModule } from '../infrastructure/redis/redis.module'; +import { UserDomainModule } from '../domain/user/user.module'; const accessTokenExpiration = TWOHOUR; export const refreshTokenExpirationInCache = 60 * 60 * 24 * 365; // 1 year @@ -29,7 +30,7 @@ export const verifyEmailExpiration = 60 * 5; signOptions: { expiresIn: accessTokenExpiration }, }), }), - UsersModule, + UserDomainModule, ContentsModule, CategoryModule, RedisModule, diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts index de108ff..f4a8787 100644 --- a/src/auth/auth.service.spec.ts +++ b/src/auth/auth.service.spec.ts @@ -8,14 +8,14 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken, TypeOrmModule } from '@nestjs/typeorm'; import { CONFIG_OPTIONS } from '../common/common.constants'; import { MailService } from '../mail/mail.service'; -import { User, UserRole } from '../users/entities/user.entity'; +import { User, UserRole } from '../domain/user/entities/user.entity'; import { DataSource, ObjectLiteral, Repository } from 'typeorm'; import { AuthService } from './auth.service'; import { customJwtService } from './jwt/jwt.service'; import { Cache } from 'cache-manager'; import { LoginBodyDto, LogoutBodyDto } from './dtos/login.dto'; -import { UsersModule } from '../users/users.module'; -import { UserRepository } from '../users/repository/user.repository'; +import { UsersModule } from '../presentation/user/users.module'; +import { UserRepository } from '../infrastructure/user/repository/user.repository'; import { TWOHOUR } from './jwt/jwt.payload'; import * as dotenv from 'dotenv'; diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 221739a..2c84977 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,5 +1,6 @@ import { BadRequestException, + Inject, Injectable, NotFoundException, UnauthorizedException, @@ -23,15 +24,15 @@ import { RefreshTokenDto, RefreshTokenOutput } from './dtos/token.dto'; import { ValidateUserDto, ValidateUserOutput } from './dtos/validate-user.dto'; import { ONEYEAR, Payload } from './jwt/jwt.payload'; import { customJwtService } from './jwt/jwt.service'; -import { UserRepository } from '../users/repository/user.repository'; -import { RedisService } from '../infra/redis/redis.service'; +import { UserRepository } from '../domain/user/user.repository'; +import { RedisService } from '../infrastructure/redis/redis.service'; import { PASSWORD_CODE_KEY, REFRESH_TOKEN_KEY } from './constants'; @Injectable() export class AuthService { constructor( private readonly jwtService: customJwtService, - private readonly userRepository: UserRepository, + @Inject(UserRepository) private readonly userRepository: UserRepository, private readonly mailService: MailService, private readonly redisService: RedisService, ) {} @@ -66,7 +67,7 @@ export class AuthService { userId: number, { refresh_token: refreshToken }: LogoutBodyDto, ): Promise { - const user = await this.userRepository.findOneBy({ id: userId }); + const user = await this.userRepository.findById(userId); if (user) { if (!refreshToken) { throw new BadRequestException('Refresh token is required'); @@ -100,7 +101,7 @@ export class AuthService { } catch (e) { throw new UnauthorizedException('Invalid refresh token'); } - const user = await this.userRepository.findOneBy({ id: decoded.sub }); + const user = await this.userRepository.findById(decoded.sub); if (!user) { throw new NotFoundException('User not found'); } @@ -140,7 +141,7 @@ export class AuthService { async sendPasswordResetEmail( email: string, ): Promise { - const user = await this.userRepository.findOneBy({ email }); + const user = await this.userRepository.findByEmail(email); if (user) { if (!user.verified) { throw new UnauthorizedException('User not verified'); @@ -167,10 +168,7 @@ export class AuthService { password, }: ValidateUserDto): Promise { try { - const user = await this.userRepository.findOne({ - where: { email }, - select: { id: true, password: true }, - }); + const user = await this.userRepository.findByEmail(email); if (!user) { throw new NotFoundException('User Not Found'); } diff --git a/src/auth/dtos/create-account.dto.ts b/src/auth/dtos/create-account.dto.ts index bd77736..470f92a 100644 --- a/src/auth/dtos/create-account.dto.ts +++ b/src/auth/dtos/create-account.dto.ts @@ -1,6 +1,6 @@ import { PickType } from '@nestjs/swagger'; import { CoreOutput } from '../../common/dtos/output.dto'; -import { User } from '../../users/entities/user.entity'; +import { User } from '../../domain/user/entities/user.entity'; // export class CreateAccountBodyDto extends PickType(User, [ // 'email', diff --git a/src/auth/dtos/login.dto.ts b/src/auth/dtos/login.dto.ts index aaf372c..547b765 100644 --- a/src/auth/dtos/login.dto.ts +++ b/src/auth/dtos/login.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty, PickType } from '@nestjs/swagger'; import { IsBoolean, IsOptional, IsString } from 'class-validator'; import { CoreOutput } from '../../common/dtos/output.dto'; -import { User } from '../../users/entities/user.entity'; +import { User } from '../../domain/user/entities/user.entity'; export class LoginBodyDto extends PickType(User, ['email', 'password']) { @ApiProperty({ diff --git a/src/auth/dtos/validate-user.dto.ts b/src/auth/dtos/validate-user.dto.ts index 65b9258..ea7cda2 100644 --- a/src/auth/dtos/validate-user.dto.ts +++ b/src/auth/dtos/validate-user.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty, PickType } from '@nestjs/swagger'; import { CoreOutput } from '../../common/dtos/output.dto'; -import { User } from '../../users/entities/user.entity'; +import { User } from '../../domain/user/entities/user.entity'; export class ValidateUserDto extends PickType(User, ['email', 'password']) {} diff --git a/src/auth/jwt/jwt.strategy.ts b/src/auth/jwt/jwt.strategy.ts index 08a09f3..cfc2f6a 100644 --- a/src/auth/jwt/jwt.strategy.ts +++ b/src/auth/jwt/jwt.strategy.ts @@ -1,12 +1,14 @@ import { ExtractJwt, Strategy } from 'passport-jwt'; -import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Payload } from './jwt.payload'; -import { UserRepository } from '../../users/repository/user.repository'; +import { UserRepository } from '../../domain/user/user.repository'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { - constructor(private readonly userRepository: UserRepository) { + constructor( + @Inject(UserRepository) private readonly userRepository: UserRepository, + ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, @@ -15,7 +17,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { } async validate(payload: Payload) { - const user = await this.userRepository.findOneBy({ id: payload.sub }); + const user = await this.userRepository.findById(payload.sub); if (user) { return user; } else { diff --git a/src/auth/oauth.service.ts b/src/auth/oauth.service.ts index 4d7b33c..9b769ec 100644 --- a/src/auth/oauth.service.ts +++ b/src/auth/oauth.service.ts @@ -3,6 +3,8 @@ import { UnauthorizedException, BadRequestException, InternalServerErrorException, + Inject, + NotFoundException, } from '@nestjs/common'; import axios from 'axios'; import { refreshTokenExpirationInCache } from './auth.module'; @@ -13,20 +15,21 @@ import { Payload } from './jwt/jwt.payload'; import { customJwtService } from './jwt/jwt.service'; import { OAuthUtil } from './util/oauth.util'; import { CategoryRepository } from '../categories/category.repository'; -import { User } from '../users/entities/user.entity'; -import { UserRepository } from '../users/repository/user.repository'; +import { User } from '../domain/user/entities/user.entity'; + import * as CryptoJS from 'crypto-js'; import { CookieOptions } from 'express'; -import { RedisService } from '../infra/redis/redis.service'; +import { RedisService } from '../infrastructure/redis/redis.service'; import { REFRESH_TOKEN_KEY } from './constants'; import { KakaoLoginRequest } from './dtos/request/kakao-login.request.dto'; import { KakaoLoginDto } from './dtos/kakao-login.dto'; +import { UserRepository } from '../domain/user/user.repository'; @Injectable() export class OAuthService { constructor( private readonly jwtService: customJwtService, - private readonly userRepository: UserRepository, + @Inject(UserRepository) private readonly userRepository: UserRepository, private readonly categoryRepository: CategoryRepository, private readonly oauthUtil: OAuthUtil, private readonly redisService: RedisService, @@ -35,7 +38,11 @@ export class OAuthService { // OAuth Login async oauthLogin(email: string): Promise { try { - const user: User = await this.userRepository.findOneByOrFail({ email }); + const user = await this.userRepository.findByEmail(email); + if (!user) { + throw new NotFoundException('User not found'); + } + if (user) { const payload: Payload = this.jwtService.createPayload( user.email, @@ -97,7 +104,7 @@ export class OAuthService { throw new BadRequestException('Please Agree to share your email'); } - let user = await this.userRepository.findOneByEmail(email); + let user = await this.userRepository.findByEmail(email); // 회원가입인 경우 기본 카테고리 생성 작업 진행 if (!user) { @@ -131,7 +138,7 @@ export class OAuthService { throw new BadRequestException('Please Agree to share your email'); } - let user = await this.userRepository.findOneByEmail(email); + let user = await this.userRepository.findByEmail(email); // 회원가입인 경우 기본 카테고리 생성 작업 진행 if (!user) { @@ -161,7 +168,7 @@ export class OAuthService { picture, }: googleUserInfo): Promise { try { - let user = await this.userRepository.findOneByEmail(email); + let user = await this.userRepository.findByEmail(email); // 회원가입인 경우 기본 카테고리 생성 작업 진행 if (!user) { @@ -229,7 +236,7 @@ export class OAuthService { const { sub: id, email } = this.jwtService.decode(data.id_token); - let user = await this.userRepository.findOneByEmail(email); + let user = await this.userRepository.findByEmail(email); if (!user) { user = new User(); diff --git a/src/auth/role.decorator.ts b/src/auth/role.decorator.ts index 8ca3821..e9596c8 100644 --- a/src/auth/role.decorator.ts +++ b/src/auth/role.decorator.ts @@ -1,5 +1,5 @@ import { SetMetadata } from '@nestjs/common'; -import { UserRole } from '../users/entities/user.entity'; +import { UserRole } from '../domain/user/entities/user.entity'; export type AllowedRoles = keyof typeof UserRole | 'Any'; diff --git a/src/auth/role.guard.ts b/src/auth/role.guard.ts index 3ac4746..14b154e 100644 --- a/src/auth/role.guard.ts +++ b/src/auth/role.guard.ts @@ -1,7 +1,7 @@ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { AllowedRoles } from './role.decorator'; -import { User } from '../users/entities/user.entity'; +import { User } from '../domain/user/entities/user.entity'; @Injectable() export class RoleGuard implements CanActivate { diff --git a/src/categories/category.controller.ts b/src/categories/category.controller.ts index 7bf178c..664267b 100644 --- a/src/categories/category.controller.ts +++ b/src/categories/category.controller.ts @@ -39,7 +39,7 @@ import { LoadPersonalCategoriesOutput, LoadFrequentCategoriesOutput, } from './dtos/load-personal-categories.dto'; -import { User } from '../users/entities/user.entity'; +import { User } from '../domain/user/entities/user.entity'; import { CategoryService } from './category.service'; @Controller('categories') diff --git a/src/categories/category.entity.ts b/src/categories/category.entity.ts index af20ffb..7a972c3 100644 --- a/src/categories/category.entity.ts +++ b/src/categories/category.entity.ts @@ -11,7 +11,7 @@ import { import { Content } from '../contents/entities/content.entity'; import { CoreEntity } from '../common/entities/core.entity'; import { Collection } from '../collections/entities/collection.entity'; -import { User } from '../users/entities/user.entity'; +import { User } from '../domain/user/entities/user.entity'; export enum IconName { None = 'None', diff --git a/src/categories/category.module.ts b/src/categories/category.module.ts index be433a9..1994b1b 100644 --- a/src/categories/category.module.ts +++ b/src/categories/category.module.ts @@ -2,19 +2,20 @@ import { Module } from '@nestjs/common'; import { ContentsModule } from '../contents/contents.module'; import { CategoryService } from './category.service'; import { OpenaiModule } from '../openai/openai.module'; -import { UsersModule } from '../users/users.module'; +import { UsersModule } from '../presentation/user/users.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Category } from './category.entity'; import { CategoryController } from './category.controller'; import { ContentRepository } from '../contents/repository/content.repository'; import { CategoryRepository } from './category.repository'; +import { UserDomainModule } from '../domain/user/user.module'; @Module({ imports: [ TypeOrmModule.forFeature([Category]), ContentsModule, OpenaiModule, - UsersModule, + UserDomainModule, ], controllers: [CategoryController], providers: [CategoryService, ContentRepository, CategoryRepository], diff --git a/src/categories/category.repository.ts b/src/categories/category.repository.ts index 1577012..e085a64 100644 --- a/src/categories/category.repository.ts +++ b/src/categories/category.repository.ts @@ -5,7 +5,7 @@ import { NotFoundException, } from '@nestjs/common'; import { Category } from './category.entity'; -import { User } from '../users/entities/user.entity'; +import { User } from '../domain/user/entities/user.entity'; import { generateSlug } from './utils/category.util'; @Injectable() diff --git a/src/categories/category.service.ts b/src/categories/category.service.ts index 4ffc064..575aead 100644 --- a/src/categories/category.service.ts +++ b/src/categories/category.service.ts @@ -2,6 +2,7 @@ import { Injectable, NotFoundException, ConflictException, + Inject, } from '@nestjs/common'; import { EntityManager } from 'typeorm'; import { @@ -25,8 +26,8 @@ import { CategoryRepository } from './category.repository'; import { ContentRepository } from '../contents/repository/content.repository'; import { getLinkContent, getLinkInfo } from '../contents/util/content.util'; import { OpenaiService } from '../openai/openai.service'; -import { User } from '../users/entities/user.entity'; -import { UserRepository } from '../users/repository/user.repository'; +import { User } from '../domain/user/entities/user.entity'; + import { generateCategoriesTree, generateSlug, @@ -34,13 +35,14 @@ import { makeCategoryListWithSaveCount, } from './utils/category.util'; import { Transactional } from '../common/aop/transactional'; +import { UserRepository } from '../domain/user/user.repository'; @Injectable() export class CategoryService { constructor( private readonly contentRepository: ContentRepository, private readonly categoryRepository: CategoryRepository, - private readonly userRepository: UserRepository, + @Inject(UserRepository) private readonly userRepository: UserRepository, private readonly openaiService: OpenaiService, ) {} @@ -51,7 +53,9 @@ export class CategoryService { entityManager?: EntityManager, ): Promise { try { - const userInDb = await this.userRepository.findOneWithCategories(user.id); + const userInDb = await this.userRepository.findByIdWithCategories( + user.id, + ); if (!userInDb) { throw new NotFoundException('User not found'); @@ -417,7 +421,9 @@ export class CategoryService { link: string, ): Promise { try { - const userInDb = await this.userRepository.findOneWithCategories(user.id); + const userInDb = await this.userRepository.findByIdWithCategories( + user.id, + ); if (!userInDb) { throw new NotFoundException('User not found'); } diff --git a/src/categories/utils/category.util.ts b/src/categories/utils/category.util.ts index 291220b..831d6e0 100644 --- a/src/categories/utils/category.util.ts +++ b/src/categories/utils/category.util.ts @@ -5,7 +5,7 @@ import { RecentCategoryList, RecentCategoryListWithSaveCount, } from '../dtos/category.dto'; -import { User } from '../../users/entities/user.entity'; +import { User } from '../../domain/user/entities/user.entity'; import { ConflictException } from '@nestjs/common'; import * as fs from 'fs'; diff --git a/src/collections/collections.service.ts b/src/collections/collections.service.ts index 1d74bcb..84a2829 100644 --- a/src/collections/collections.service.ts +++ b/src/collections/collections.service.ts @@ -4,7 +4,7 @@ import { Injectable, NotFoundException, } from '@nestjs/common'; -import { User } from 'src/users/entities/user.entity'; +import { User } from 'src/domain/user/entities/user.entity'; import { EntityManager } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; import { diff --git a/src/collections/entities/collection.entity.ts b/src/collections/entities/collection.entity.ts index bcfbf68..26e2f8e 100644 --- a/src/collections/entities/collection.entity.ts +++ b/src/collections/entities/collection.entity.ts @@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { CoreEntity } from '../../common/entities/core.entity'; import { NestedContent } from './nested-content.entity'; -import { User } from '../../users/entities/user.entity'; +import { User } from '../../domain/user/entities/user.entity'; import { Column, Entity, ManyToOne, OneToMany, RelationId } from 'typeorm'; import { Category } from '../../categories/category.entity'; diff --git a/src/contents/contents.controller.ts b/src/contents/contents.controller.ts index f99f590..778a3fa 100644 --- a/src/contents/contents.controller.ts +++ b/src/contents/contents.controller.ts @@ -26,7 +26,7 @@ import { AuthUser } from '../auth/auth-user.decorator'; import { JwtAuthGuard } from '../auth/jwt/jwt.guard'; import { TransactionInterceptor } from '../common/interceptors/transaction.interceptor'; import { TransactionManager } from '../common/transaction.decorator'; -import { User } from '../users/entities/user.entity'; +import { User } from '../domain/user/entities/user.entity'; import { EntityManager } from 'typeorm'; import { ContentsService } from './contents.service'; import { diff --git a/src/contents/contents.module.ts b/src/contents/contents.module.ts index 71e5f5e..981745f 100644 --- a/src/contents/contents.module.ts +++ b/src/contents/contents.module.ts @@ -5,11 +5,15 @@ import { ContentsService } from './contents.service'; import { Content } from './entities/content.entity'; import { ContentRepository } from './repository/content.repository'; import { CategoryRepository } from '../categories/category.repository'; -import { UsersModule } from '../users/users.module'; import { OpenaiModule } from '../openai/openai.module'; +import { UserDomainModule } from '../domain/user/user.module'; @Module({ - imports: [TypeOrmModule.forFeature([Content]), UsersModule, OpenaiModule], + imports: [ + TypeOrmModule.forFeature([Content]), + UserDomainModule, + OpenaiModule, + ], controllers: [ContentsController], providers: [ContentsService, ContentRepository, CategoryRepository], exports: [ContentsService], diff --git a/src/contents/contents.service.spec.ts b/src/contents/contents.service.spec.ts index 939a933..1817b16 100644 --- a/src/contents/contents.service.spec.ts +++ b/src/contents/contents.service.spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CONFIG_OPTIONS } from '../common/common.constants'; import { SummaryService } from '../summary/summary.service'; -import { User, UserRole } from '../users/entities/user.entity'; +import { User, UserRole } from '../domain/user/entities/user.entity'; import { DataSource, EntityManager, ObjectLiteral, Repository } from 'typeorm'; import { CategoryService, ContentsService } from './contents.service'; import { Content } from './entities/content.entity'; diff --git a/src/contents/contents.service.ts b/src/contents/contents.service.ts index 7dca2f7..7427db9 100644 --- a/src/contents/contents.service.ts +++ b/src/contents/contents.service.ts @@ -1,6 +1,7 @@ import { BadRequestException, ForbiddenException, + Inject, Injectable, NotFoundException, } from '@nestjs/common'; @@ -21,11 +22,11 @@ import { LoadPersonalContentsOutput, } from './dtos/load-personal-contents.dto'; import { SummaryService } from '../summary/summary.service'; -import { User } from '../users/entities/user.entity'; +import { User } from '../domain/user/entities/user.entity'; import { Category } from '../categories/category.entity'; import { Content } from './entities/content.entity'; import { LoadReminderCountOutput } from './dtos/load-personal-remider-count.dto'; -import { UserRepository } from '../users/repository/user.repository'; +import { UserRepository } from '../domain/user/user.repository'; import { ContentRepository } from './repository/content.repository'; import { CategoryRepository } from '../categories/category.repository'; import { getLinkInfo } from './util/content.util'; @@ -36,7 +37,7 @@ import { Transactional } from '../common/aop/transactional'; @Injectable() export class ContentsService { constructor( - private readonly userRepository: UserRepository, + @Inject(UserRepository) private readonly userRepository: UserRepository, private readonly contentRepository: ContentRepository, private readonly summaryService: SummaryService, private readonly categoryRepository: CategoryRepository, @@ -234,7 +235,7 @@ export class ContentsService { entityManager?: EntityManager, ): Promise { try { - const userInDb = await this.userRepository.findOneWithContents(user.id); + const userInDb = await this.userRepository.findByIdWithContents(user.id); if (!userInDb) { throw new NotFoundException('User not found'); @@ -345,7 +346,7 @@ export class ContentsService { contentId: number, ): Promise { try { - const userInDb = await this.userRepository.findOneWithContents(user.id); + const userInDb = await this.userRepository.findByIdWithContents(user.id); if (!userInDb) { throw new NotFoundException('User not found'); } diff --git a/src/contents/entities/content.entity.ts b/src/contents/entities/content.entity.ts index e261094..d4492ef 100644 --- a/src/contents/entities/content.entity.ts +++ b/src/contents/entities/content.entity.ts @@ -6,7 +6,7 @@ import { IsUrl, } from 'class-validator'; import { CoreEntity } from '../../common/entities/core.entity'; -import { User } from '../../users/entities/user.entity'; +import { User } from '../../domain/user/entities/user.entity'; import { Column, Entity, JoinColumn, ManyToOne, RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { Category } from '../../categories/category.entity'; diff --git a/src/database/typerom-config.service.ts b/src/database/typerom-config.service.ts index d418189..ae1be04 100644 --- a/src/database/typerom-config.service.ts +++ b/src/database/typerom-config.service.ts @@ -5,8 +5,8 @@ import { Collection } from '../collections/entities/collection.entity'; import { NestedContent } from '../collections/entities/nested-content.entity'; import { Category } from '../categories/category.entity'; import { Content } from '../contents/entities/content.entity'; -import { User } from '../users/entities/user.entity'; -import { PaidPlan } from '../users/entities/paid-plan.entity'; +import { User } from '../domain/user/entities/user.entity'; +import { PaidPlan } from '../domain/user/entities/paid-plan.entity'; @Injectable() export class TypeOrmConfigService implements TypeOrmOptionsFactory { diff --git a/src/users/entities/paid-plan.entity.ts b/src/domain/user/entities/paid-plan.entity.ts similarity index 93% rename from src/users/entities/paid-plan.entity.ts rename to src/domain/user/entities/paid-plan.entity.ts index edef111..6eb5dde 100644 --- a/src/users/entities/paid-plan.entity.ts +++ b/src/domain/user/entities/paid-plan.entity.ts @@ -1,7 +1,7 @@ import { Column, Entity, OneToMany } from 'typeorm'; import { IsNumber, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CoreEntity } from '../../common/entities/core.entity'; +import { CoreEntity } from '../../../common/entities/core.entity'; import { User } from './user.entity'; @Entity() diff --git a/src/users/entities/user.entity.ts b/src/domain/user/entities/user.entity.ts similarity index 91% rename from src/users/entities/user.entity.ts rename to src/domain/user/entities/user.entity.ts index c321e14..9a143d7 100644 --- a/src/users/entities/user.entity.ts +++ b/src/domain/user/entities/user.entity.ts @@ -10,10 +10,10 @@ import { import * as bcrypt from 'bcrypt'; import { IsBoolean, IsEmail, IsEnum, IsString, Matches } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { Content } from '../../contents/entities/content.entity'; -import { Category } from '../../categories/category.entity'; -import { Collection } from '../../collections/entities/collection.entity'; -import { CoreEntity } from '../../common/entities/core.entity'; +import { Content } from '../../../contents/entities/content.entity'; +import { Category } from '../../../categories/category.entity'; +import { Collection } from '../../../collections/entities/collection.entity'; +import { CoreEntity } from '../../../common/entities/core.entity'; import { PaidPlan } from './paid-plan.entity'; export enum UserRole { diff --git a/src/domain/user/user.module.ts b/src/domain/user/user.module.ts new file mode 100644 index 0000000..eda5386 --- /dev/null +++ b/src/domain/user/user.module.ts @@ -0,0 +1,24 @@ +import { Module } from '@nestjs/common'; +import { UsersService } from './users.service'; +import { UserRepository } from './user.repository'; +import { UserRepositoryImpl } from '../../infrastructure/user/repository/user.repository'; +import { RedisModule } from '../../infrastructure/redis/redis.module'; + +@Module({ + imports: [RedisModule], + providers: [ + UsersService, + { + provide: UserRepository, + useClass: UserRepositoryImpl, + }, + ], + exports: [ + UsersService, + { + provide: UserRepository, + useClass: UserRepositoryImpl, + }, + ], +}) +export class UserDomainModule {} diff --git a/src/domain/user/user.repository.ts b/src/domain/user/user.repository.ts new file mode 100644 index 0000000..0628438 --- /dev/null +++ b/src/domain/user/user.repository.ts @@ -0,0 +1,14 @@ +import { User } from './entities/user.entity'; + +export interface UserRepository { + findById(id: number): Promise; + findByIdWithContents(id: number): Promise; + findByIdWithCategories(id: number): Promise; + findByIdWithCategoriesOrFail(id: number): Promise; + findOneWithContentsAndCategories(id: number): Promise; + findByEmail(email: string): Promise; + createOne(user: User): Promise; + deleteById(id: number): Promise; +} + +export const UserRepository = Symbol('UserRepository'); diff --git a/src/users/users.service.spec.ts b/src/domain/user/users.service.spec.ts similarity index 100% rename from src/users/users.service.spec.ts rename to src/domain/user/users.service.spec.ts diff --git a/src/users/users.service.ts b/src/domain/user/users.service.ts similarity index 76% rename from src/users/users.service.ts rename to src/domain/user/users.service.ts index c202478..b33431d 100644 --- a/src/users/users.service.ts +++ b/src/domain/user/users.service.ts @@ -1,24 +1,27 @@ import { + Inject, Injectable, NotFoundException, UnauthorizedException, } from '@nestjs/common'; import { EntityManager } from 'typeorm'; -import { EditProfileDto, EditProfileOutput } from './dtos/edit-profile.dto'; +import { + EditProfileDto, + EditProfileOutput, +} from '../../users/dtos/edit-profile.dto'; import { ResetPasswordInput, ResetPasswordOutput, -} from './dtos/reset-password.dto'; +} from '../../users/dtos/reset-password.dto'; import { User } from './entities/user.entity'; -import { DeleteAccountOutput } from './dtos/delete-account.dto'; -import { UserRepository } from './repository/user.repository'; -import { RedisService } from '../infra/redis/redis.service'; -import { PASSWORD_CODE_KEY } from '../auth/constants'; +import { RedisService } from '../../infrastructure/redis/redis.service'; +import { PASSWORD_CODE_KEY } from '../../auth/constants'; +import { UserRepository } from './user.repository'; @Injectable() export class UsersService { constructor( - private readonly userRepository: UserRepository, + @Inject(UserRepository) private readonly userRepository: UserRepository, private readonly redisService: RedisService, ) {} @@ -78,13 +81,13 @@ export class UsersService { } } - async deleteAccount(userId: number): Promise { - const { affected } = await this.userRepository.delete(userId); + async deleteAccount(userId: number): Promise { + const user = await this.userRepository.findByIdWithContents(userId); - if (affected === 1) { - return {}; - } else { + if (!user) { throw new NotFoundException('User not found'); } + + await this.userRepository.deleteById(userId); } } diff --git a/src/infra/image/dto/get-upload-url.dto.ts b/src/infrastructure/image/dto/get-upload-url.dto.ts similarity index 100% rename from src/infra/image/dto/get-upload-url.dto.ts rename to src/infrastructure/image/dto/get-upload-url.dto.ts diff --git a/src/infra/image/dto/response/get-upload-url.response.dto.ts b/src/infrastructure/image/dto/response/get-upload-url.response.dto.ts similarity index 100% rename from src/infra/image/dto/response/get-upload-url.response.dto.ts rename to src/infrastructure/image/dto/response/get-upload-url.response.dto.ts diff --git a/src/infra/image/dto/upload-image.dto.ts b/src/infrastructure/image/dto/upload-image.dto.ts similarity index 100% rename from src/infra/image/dto/upload-image.dto.ts rename to src/infrastructure/image/dto/upload-image.dto.ts diff --git a/src/infra/image/image.module.ts b/src/infrastructure/image/image.module.ts similarity index 100% rename from src/infra/image/image.module.ts rename to src/infrastructure/image/image.module.ts diff --git a/src/infra/image/image.service.interface.ts b/src/infrastructure/image/image.service.interface.ts similarity index 100% rename from src/infra/image/image.service.interface.ts rename to src/infrastructure/image/image.service.interface.ts diff --git a/src/infra/image/s3.service.spec.ts b/src/infrastructure/image/s3.service.spec.ts similarity index 100% rename from src/infra/image/s3.service.spec.ts rename to src/infrastructure/image/s3.service.spec.ts diff --git a/src/infra/image/s3.service.ts b/src/infrastructure/image/s3.service.ts similarity index 100% rename from src/infra/image/s3.service.ts rename to src/infrastructure/image/s3.service.ts diff --git a/src/infra/infra.module.ts b/src/infrastructure/infra.module.ts similarity index 100% rename from src/infra/infra.module.ts rename to src/infrastructure/infra.module.ts diff --git a/src/infra/redis/redis.factory.ts b/src/infrastructure/redis/redis.factory.ts similarity index 100% rename from src/infra/redis/redis.factory.ts rename to src/infrastructure/redis/redis.factory.ts diff --git a/src/infra/redis/redis.module.ts b/src/infrastructure/redis/redis.module.ts similarity index 100% rename from src/infra/redis/redis.module.ts rename to src/infrastructure/redis/redis.module.ts diff --git a/src/infra/redis/redis.service.ts b/src/infrastructure/redis/redis.service.ts similarity index 100% rename from src/infra/redis/redis.service.ts rename to src/infrastructure/redis/redis.service.ts diff --git a/src/users/repository/user.repository.ts b/src/infrastructure/user/repository/user.repository.ts similarity index 63% rename from src/users/repository/user.repository.ts rename to src/infrastructure/user/repository/user.repository.ts index c2a508d..14836e5 100644 --- a/src/users/repository/user.repository.ts +++ b/src/infrastructure/user/repository/user.repository.ts @@ -1,29 +1,36 @@ import { DataSource, Repository } from 'typeorm'; -import { User } from '../entities/user.entity'; +import { User } from '../../../domain/user/entities/user.entity'; import { Injectable } from '@nestjs/common'; -import { GetOrCreateAccountBodyDto } from '../dtos/get-or-create-account.dto'; +import { UserRepository } from '../../../domain/user/user.repository'; @Injectable() -export class UserRepository extends Repository { +export class UserRepositoryImpl + extends Repository + implements UserRepository +{ constructor(private dataSource: DataSource) { super(User, dataSource.createEntityManager()); } - async findOneWithContents(id: number): Promise { + async findById(id: number): Promise { + return this.findById(id); + } + + async findByIdWithContents(id: number): Promise { return await this.createQueryBuilder('user') .leftJoinAndSelect('user.contents', 'content') .where('user.id = :id', { id }) .getOne(); } - async findOneWithCategories(id: number): Promise { + async findByIdWithCategories(id: number): Promise { return await this.createQueryBuilder('user') .leftJoinAndSelect('user.categories', 'categories') .where('user.id = :id', { id }) .getOne(); } - async findOneWithCategoriesOrFail(id: number): Promise { + async findByIdWithCategoriesOrFail(id: number): Promise { return await this.createQueryBuilder('user') .leftJoinAndSelect('user.categories', 'categories') .where('user.id = :id', { id }) @@ -31,6 +38,10 @@ export class UserRepository extends Repository { .getOneOrFail(); } + async findByEmail(email: string): Promise { + return this.findOne({ where: { email } }); + } + async findOneWithContentsAndCategories(id: number): Promise { return await this.createQueryBuilder('user') .leftJoinAndSelect('user.contents', 'content') @@ -44,7 +55,11 @@ export class UserRepository extends Repository { return this.findOne({ where: { email } }); } - async createOne(user: Partial): Promise { + async createOne(user: User): Promise { return this.save(user); } + + async deleteById(id: number): Promise { + await this.delete(id); + } } diff --git a/src/users/users.controller.ts b/src/presentation/user/users.controller.ts similarity index 75% rename from src/users/users.controller.ts rename to src/presentation/user/users.controller.ts index 4df26cc..7113302 100644 --- a/src/users/users.controller.ts +++ b/src/presentation/user/users.controller.ts @@ -16,21 +16,24 @@ import { ApiOperation, ApiTags, } from '@nestjs/swagger'; -import { AuthUser } from '../auth/auth-user.decorator'; -import { JwtAuthGuard } from '../auth/jwt/jwt.guard'; -import { TransactionInterceptor } from '../common/interceptors/transaction.interceptor'; -import { TransactionManager } from '../common/transaction.decorator'; +import { AuthUser } from '../../auth/auth-user.decorator'; +import { JwtAuthGuard } from '../../auth/jwt/jwt.guard'; +import { TransactionInterceptor } from '../../common/interceptors/transaction.interceptor'; +import { TransactionManager } from '../../common/transaction.decorator'; import { EntityManager } from 'typeorm'; -import { EditProfileDto, EditProfileOutput } from './dtos/edit-profile.dto'; -import { meOutput } from './dtos/me.dto'; +import { + EditProfileDto, + EditProfileOutput, +} from '../../users/dtos/edit-profile.dto'; +import { meOutput } from '../../users/dtos/me.dto'; import { ResetPasswordInput, ResetPasswordOutput, -} from './dtos/reset-password.dto'; -import { User } from './entities/user.entity'; -import { UsersService } from './users.service'; -import { DeleteAccountOutput } from './dtos/delete-account.dto'; -import { ErrorOutput } from '../common/dtos/output.dto'; +} from '../../users/dtos/reset-password.dto'; +import { User } from '../../domain/user/entities/user.entity'; +import { UsersService } from '../../domain/user/users.service'; +import { DeleteAccountOutput } from '../../users/dtos/delete-account.dto'; +import { ErrorOutput } from '../../common/dtos/output.dto'; @Controller('user') @ApiTags('User') @@ -103,6 +106,8 @@ export class UsersController { @UseGuards(JwtAuthGuard) @Delete() async deleteAccount(@AuthUser() user: User): Promise { - return this.usersService.deleteAccount(user.id); + await this.usersService.deleteAccount(user.id); + + return {}; } } diff --git a/src/presentation/user/users.module.ts b/src/presentation/user/users.module.ts new file mode 100644 index 0000000..8769817 --- /dev/null +++ b/src/presentation/user/users.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { UsersController } from './users.controller'; +import { RedisModule } from '../../infrastructure/redis/redis.module'; +import { UserDomainModule } from '../../domain/user/user.module'; + +@Module({ + imports: [RedisModule, UserDomainModule], + controllers: [UsersController], +}) +export class UsersModule {} diff --git a/src/users/dtos/get-or-create-account.dto.ts b/src/users/dtos/get-or-create-account.dto.ts index 3f83faf..a459164 100644 --- a/src/users/dtos/get-or-create-account.dto.ts +++ b/src/users/dtos/get-or-create-account.dto.ts @@ -1,5 +1,5 @@ import { PickType } from '@nestjs/swagger'; -import { User } from '../entities/user.entity'; +import { User } from '../../domain/user/entities/user.entity'; export class GetOrCreateAccountBodyDto extends PickType(User, [ 'email', diff --git a/src/users/dtos/me.dto.ts b/src/users/dtos/me.dto.ts index 434c1ac..8c96986 100644 --- a/src/users/dtos/me.dto.ts +++ b/src/users/dtos/me.dto.ts @@ -1,4 +1,4 @@ import { OmitType } from '@nestjs/swagger'; -import { User } from '../entities/user.entity'; +import { User } from '../../domain/user/entities/user.entity'; export class meOutput extends OmitType(User, ['password']) {} diff --git a/src/users/dtos/reset-password.dto.ts b/src/users/dtos/reset-password.dto.ts index 3bccafd..06ff68e 100644 --- a/src/users/dtos/reset-password.dto.ts +++ b/src/users/dtos/reset-password.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty, IntersectionType, PickType } from '@nestjs/swagger'; import { CoreOutput } from '../../common/dtos/output.dto'; -import { User } from '../entities/user.entity'; +import { User } from '../../domain/user/entities/user.entity'; class passwordForResetPassword extends PickType(User, ['password']) {} class codeForResetPassword { diff --git a/src/users/users.module.ts b/src/users/users.module.ts deleted file mode 100644 index 51b00b7..0000000 --- a/src/users/users.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; -import { UsersController } from './users.controller'; -import { UsersService } from './users.service'; -import { UserRepository } from './repository/user.repository'; -import { RedisModule } from '../infra/redis/redis.module'; - -@Module({ - imports: [RedisModule], - controllers: [UsersController], - providers: [UsersService, UserRepository], - exports: [UserRepository], -}) -export class UsersModule {} diff --git a/test/auth/get-kakao-login.spec.ts b/test/auth/get-kakao-login.spec.ts index f6de45f..a849155 100644 --- a/test/auth/get-kakao-login.spec.ts +++ b/test/auth/get-kakao-login.spec.ts @@ -8,7 +8,7 @@ import { } from '@nestjs/common'; import { StartedPostgreSqlContainer } from '@testcontainers/postgresql'; import { OAuthUtil } from '../../src/auth/util/oauth.util'; -import { User } from '../../src/users/entities/user.entity'; +import { User } from '../../src/domain/user/entities/user.entity'; import * as request from 'supertest'; import { cacheManagerMock } from '../mock/cache-manager.mock'; import { oAuthUtilMock } from '../mock/oauth-util.mock'; diff --git a/test/auth/post-login.spec.ts b/test/auth/post-login.spec.ts index 93eb555..b8c53fd 100644 --- a/test/auth/post-login.spec.ts +++ b/test/auth/post-login.spec.ts @@ -1,7 +1,7 @@ import { getBuilder } from '../common/application-builder'; import { CACHE_MANAGER, HttpStatus, INestApplication } from '@nestjs/common'; import { cacheManagerMock } from '../mock/cache-manager.mock'; -import { User } from '../../src/users/entities/user.entity'; +import { User } from '../../src/domain/user/entities/user.entity'; import { DataSource, Repository } from 'typeorm'; import { StartedPostgreSqlContainer } from '@testcontainers/postgresql'; import * as request from 'supertest'; diff --git a/test/auth/post-logout.spec.ts b/test/auth/post-logout.spec.ts index 04f3947..69ed2f2 100644 --- a/test/auth/post-logout.spec.ts +++ b/test/auth/post-logout.spec.ts @@ -1,7 +1,7 @@ import { CACHE_MANAGER, HttpStatus, INestApplication } from '@nestjs/common'; import { StartedPostgreSqlContainer } from '@testcontainers/postgresql'; import { DataSource, Repository } from 'typeorm'; -import { User } from '../../src/users/entities/user.entity'; +import { User } from '../../src/domain/user/entities/user.entity'; import { Seeder } from '../seeder/seeder.interface'; import { UserSeeder } from '../seeder/user.seeder'; import { getBuilder } from '../common/application-builder'; diff --git a/test/auth/post-send-password-reset-email.spec.ts b/test/auth/post-send-password-reset-email.spec.ts index 667502e..3222410 100644 --- a/test/auth/post-send-password-reset-email.spec.ts +++ b/test/auth/post-send-password-reset-email.spec.ts @@ -2,7 +2,7 @@ import { CACHE_MANAGER, HttpStatus, INestApplication } from '@nestjs/common'; import { StartedPostgreSqlContainer } from '@testcontainers/postgresql'; import { DataSource, Repository } from 'typeorm'; import { Cache } from 'cache-manager'; -import { User } from '../../src/users/entities/user.entity'; +import { User } from '../../src/domain/user/entities/user.entity'; import { Seeder } from '../seeder/seeder.interface'; import { UserSeeder } from '../seeder/user.seeder'; import { getBuilder } from '../common/application-builder'; diff --git a/test/auth/post-token.spec.ts b/test/auth/post-token.spec.ts index 2a02eca..631c034 100644 --- a/test/auth/post-token.spec.ts +++ b/test/auth/post-token.spec.ts @@ -2,7 +2,7 @@ import { CACHE_MANAGER, HttpStatus, INestApplication } from '@nestjs/common'; import { StartedPostgreSqlContainer } from '@testcontainers/postgresql'; import { DataSource, Repository } from 'typeorm'; import { Cache } from 'cache-manager'; -import { User } from '../../src/users/entities/user.entity'; +import { User } from '../../src/domain/user/entities/user.entity'; import { Seeder } from '../seeder/seeder.interface'; import { UserSeeder } from '../seeder/user.seeder'; import { getBuilder } from '../common/application-builder'; diff --git a/test/contents/post-multiple.spec.ts b/test/contents/post-multiple.spec.ts index 3ba459d..73f41ee 100644 --- a/test/contents/post-multiple.spec.ts +++ b/test/contents/post-multiple.spec.ts @@ -6,7 +6,7 @@ import { getBuilder } from '../common/application-builder'; import { JwtAuthGuard } from '../../src/auth/jwt/jwt.guard'; import { jwtAuthGuardMock } from '../mock/jwt-auth-guard.mock'; import { UserSeeder } from '../seeder/user.seeder'; -import { User } from '../../src/users/entities/user.entity'; +import { User } from '../../src/domain/user/entities/user.entity'; import { AddMultipleContentsBodyDto } from '../../src/contents/dtos/content.dto'; import { faker } from '@faker-js/faker'; import { Category } from '../../src/categories/category.entity'; diff --git a/test/contents/post.spec.ts b/test/contents/post.spec.ts index 3420cba..b82c7c6 100644 --- a/test/contents/post.spec.ts +++ b/test/contents/post.spec.ts @@ -6,7 +6,7 @@ import { getBuilder } from '../common/application-builder'; import { JwtAuthGuard } from '../../src/auth/jwt/jwt.guard'; import { jwtAuthGuardMock } from '../mock/jwt-auth-guard.mock'; import { UserSeeder } from '../seeder/user.seeder'; -import { User } from '../../src/users/entities/user.entity'; +import { User } from '../../src/domain/user/entities/user.entity'; import { AddContentBodyDto } from '../../src/contents/dtos/content.dto'; import { faker } from '@faker-js/faker'; import { Category } from '../../src/categories/category.entity'; diff --git a/test/mock/jwt-auth-guard.mock.ts b/test/mock/jwt-auth-guard.mock.ts index 63d5f2f..84d8c05 100644 --- a/test/mock/jwt-auth-guard.mock.ts +++ b/test/mock/jwt-auth-guard.mock.ts @@ -1,5 +1,5 @@ import { ExecutionContext } from '@nestjs/common'; -import { User } from '../../src/users/entities/user.entity'; +import { User } from '../../src/domain/user/entities/user.entity'; export const jwtAuthGuardMock = (user: User) => ({ canActivate(context: ExecutionContext) { diff --git a/test/seeder/user.seeder.ts b/test/seeder/user.seeder.ts index 12d2475..f807119 100644 --- a/test/seeder/user.seeder.ts +++ b/test/seeder/user.seeder.ts @@ -1,5 +1,5 @@ import { Seeder } from './seeder.interface'; -import { User, UserRole } from '../../src/users/entities/user.entity'; +import { User, UserRole } from '../../src/domain/user/entities/user.entity'; import { faker } from '@faker-js/faker'; export class UserSeeder implements Seeder {