Skip to content

Commit

Permalink
merge: 프로젝트 완성 (#81)
Browse files Browse the repository at this point in the history
* refactor(s3, redis): Dynamic module

* fix: not optional only null...

* refactor(config): jwt expires 수정 (#64)

* refactor(ignore): .gitignore & .dockerignore

* fix: Validation에서 ApiException throw하도록

* docs(README): docker registry 변경

* refactor: Redis Module 수정 (#65)

* refactor(redis): 비밀번호 추가

* refactor(redis): set에서 ttl설정하도록 로직 변경

* docs: 도커에 Redis Password 추가

* test: RedisService 수정

* ci: Github Workflow 수정 (#66)

* chore(github): Issue Template Label 수정

* ci: 배포 workflow 수정

* feat: Activity Study 구현 (#68)

* feat(schema): Activity Schema 구현

* feat(repository): Activity Repository 구현

* fix(mongoose): MongooseModule#async를 sync로 작동하도록 변경

* refactor(activity): 도메인 분리(project, study, social)

* style(auth): Role을 schema 폴더로 이동

* feat(activity): Study 구현

* feat(activity): Category 구현

* fix(activity): Activity 공통 스키마 수정 (author 분리)

* fix(activity/study): findAll 시 upload date 기준으로 sort

* refactor: 전체적인 코드 리뷰 (#69)

* refactor: remove unused import

* refactor(eslint): 문법 수정

* refactor(auth): 중복 코드 삭제

* refactor(study): 사용하지 않는 메소드 삭제

* refactor(member): repository 메소드 네이밍 통일

* fix(activity/study): author 시그니처 변경

* fix(activity/study): controller에서 member 그만 받기

* fix(activity/study): 중복된 study가 생성되지 않도록 수정

* feat: Activity 나머지(project, social) 구현 (#71)

* feat(activity/project): Project 구현

* feat(activity/social): Social 구현

* refactor: page를 body로 받기

* feat: File Upload 구현 (#72)

* fix: File Upload 수정 (#73)

* feat: File Upload 구현

* fix: multipart 관련 오류 수정

* style: 코드 스타일 수정

* fix(mongodb): 데이터 시간대를 KST로 설정 (#74)

* feat(event): Activity 도메인들 Event 기반 로깅 적용 (#75)

* feat(job): add purge-unused-image job (#76)

* refactor: 모든 패키지 latest로 업그레이드 (#77)

* refactor: Docker Image 크기 최적화 (#78)

* feat: prettier import order 플러그인 추가 (#79)

* refactor: Docker Image 크기 최적화 (#78)

* feat: prettier import order 플러그인 추가
  • Loading branch information
son-daehyeon authored Sep 15, 2024
1 parent 097305f commit 0ff5531
Show file tree
Hide file tree
Showing 175 changed files with 3,995 additions and 1,404 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ RUN apk add --no-cache curl \
COPY --from=build /app/config/config.template.yaml ./config/config.template.yaml

COPY --from=build /app/package.json /app/yarn.lock ./
RUN yarn install --production --frozen-lockfile
RUN yarn install --production --frozen-lockfile && yarn cache clean

COPY --from=build /app/dist ./dist

Expand Down
20 changes: 7 additions & 13 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,11 @@ import tseslint from 'typescript-eslint';
import prettierConfig from 'eslint-config-prettier';
import prettierRecommended from 'eslint-plugin-prettier/recommended';

export default tseslint.config(
{
files: ['**/*.js', '**/*.mjs', '**/*.ts'],
extends: [eslint.configs.recommended, ...tseslint.configs.recommended],
rules: {
'no-console': 'warn',
},
export default tseslint.config({
files: ['**/*.js', '**/*.mjs', '**/*.ts'],
extends: [eslint.configs.recommended, ...tseslint.configs.recommended, prettierRecommended],
rules: {
...prettierConfig.rules,
'no-console': 'warn',
},
{
files: ['**/*.js', '**/*.mjs', '**/*.ts'],
extends: [prettierRecommended],
rules: prettierConfig.rules,
},
);
});
56 changes: 30 additions & 26 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,71 +9,75 @@
"start:dev": "nest start --watch",
"start:prod": "node dist/main",
"test": "jest --runInBand",
"format": "prettier --write \"{src,spec}/**/*.ts\"",
"lint": "eslint \"{src,spec}/**/*.ts\" --fix"
"format": "prettier --write \"{src,test}/**/*.ts\"",
"lint": "eslint \"{src,test}/**/*.ts\" --fix"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.623.0",
"@nestjs/common": "^10.3.10",
"@aws-sdk/client-s3": "^3.651.1",
"@nestjs/common": "^10.4.1",
"@nestjs/config": "^3.2.3",
"@nestjs/core": "^10.3.10",
"@nestjs/core": "^10.4.1",
"@nestjs/event-emitter": "^2.0.4",
"@nestjs/jwt": "^10.2.0",
"@nestjs/mongoose": "^10.0.10",
"@nestjs/platform-express": "^10.3.10",
"@nestjs/platform-express": "^10.4.1",
"@nestjs/schedule": "^4.1.0",
"@nestjs/swagger": "^7.4.0",
"axios": "^1.7.7",
"bcrypt": "^5.1.1",
"cheerio": "^1.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"ioredis": "^5.4.1",
"js-yaml": "^4.1.0",
"mongoose": "^8.5.1",
"mongoose": "^8.6.2",
"mongoose-autopopulate": "^1.1.0",
"ms": "^3.0.0-canary.1",
"nest-winston": "^1.9.7",
"nodemailer": "^6.9.14",
"nodemailer": "^6.9.15",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"uuid": "^10.0.0",
"winston": "^3.13.1",
"winston": "^3.14.2",
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
"@eslint/js": "^9.8.0",
"@nestjs/cli": "^10.4.2",
"@nestjs/schematics": "^10.1.2",
"@nestjs/testing": "^10.3.10",
"@swc/cli": "^0.4.0",
"@swc/core": "^1.7.3",
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"@eslint/js": "^9.10.0",
"@nestjs/cli": "^10.4.5",
"@nestjs/schematics": "^10.1.4",
"@nestjs/testing": "^10.4.1",
"@swc/cli": "^0.4.1-nightly.20240914",
"@swc/core": "^1.7.26",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/bcrypt": "^5.0.2",
"@types/cheerio": "^0.22.35",
"@types/eslint-config-prettier": "^6.11.3",
"@types/eslint__js": "^8.42.3",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.12",
"@types/jest": "^29.5.13",
"@types/js-yaml": "^4.0.9",
"@types/multer": "^1.4.11",
"@types/node": "^20.14.10",
"@types/multer": "^1.4.12",
"@types/node": "^22.5.5",
"@types/nodemailer": "^6.4.15",
"@types/supertest": "^6.0.2",
"@types/uuid": "^10.0.0",
"eslint": "^9.8.0",
"eslint": "^9.10.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"husky": "^9.0.11",
"husky": "^9.1.6",
"jest": "^29.7.0",
"lint-staged": "^15.2.7",
"lint-staged": "^15.2.10",
"prettier": "^3.3.3",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.2",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.5.4",
"typescript-eslint": "^7.17.0"
"typescript": "^5.6.2",
"typescript-eslint": "^8.5.0"
},
"resolutions": {
"webpack": "^5.0.0",
Expand Down
28 changes: 28 additions & 0 deletions prettier.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,32 @@ export default {
tabWidth: 2,
trailingComma: 'all',
useTabs: false,
plugins: ['@trivago/prettier-plugin-sort-imports'],
importOrderParserPlugins: ['typescript', 'decorators-legacy'],
importOrder: [
// Nest
'^@nestjs',

// Domain
'^@wink/auth',
'^@wink/member',
'^@wink/activity',

// Common
'^@wink/(app|config|filter|interceptor|mongo|redis|s3)',

// Utils
'^@wink/(event|logger|mail|swagger|validation)',

// Test
'^@wink/test-mock',

// Other
'^[./]',

// Libraries
'<THIRD_PARTY_MODULES>',
],
importOrderSeparation: true,
importOrderSortSpecifiers: true,
};
16 changes: 9 additions & 7 deletions src/common/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { APP_PIPE, APP_INTERCEPTOR, APP_FILTER } from '@nestjs/core';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigModuleOptions } from '@nestjs/config';
import { MongooseModule, MongooseModuleAsyncOptions } from '@nestjs/mongoose';
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { JwtModule, JwtModuleAsyncOptions } from '@nestjs/jwt';
import { MongooseModule, MongooseModuleAsyncOptions } from '@nestjs/mongoose';
import { ScheduleModule } from '@nestjs/schedule';
import { EventEmitterModule } from '@nestjs/event-emitter';

import { AppConfig, JwtConfig, MongoConfig } from '@wink/config';
import { DefaultExceptionFilter, NotFoundExceptionFilter } from '@wink/filter';
import { ApiResponseInterceptor } from '@wink/interceptor';

import { AuthModule } from '@wink/auth/auth.module';

import { MemberModule } from '@wink/member/member.module';

import { ActivityModule } from '@wink/activity/activity.module';

import { AppConfig, JwtConfig, MongoConfig } from '@wink/config';
import { DefaultExceptionFilter, NotFoundExceptionFilter } from '@wink/filter';
import { ApiResponseInterceptor } from '@wink/interceptor';

import { EventListenerModule } from '@wink/event';
import { Validation } from '@wink/validation';

Expand Down
2 changes: 1 addition & 1 deletion src/common/config/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { load } from 'js-yaml';
import { join } from 'path';

type ConfigType = Record<string, unknown>;

Expand Down
52 changes: 43 additions & 9 deletions src/common/database/mongo/mongo-model.factory.util.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,54 @@
import { AsyncModelFactory } from '@nestjs/mongoose';
import { ModelDefinition } from '@nestjs/mongoose';

import { Schema } from 'mongoose';
import AutoPopulate from 'mongoose-autopopulate';

const convertToKST = (date: Date): Date => new Date(date.getTime() + 9 * 60 * 60 * 1000);

export class MongoModelFactory {
static generate<T>(name: string, schema: Schema<T>): AsyncModelFactory {
static generate<T>(name: string, schema: Schema<T>): ModelDefinition {
schema = MongoModelFactory.setSchemaOptions(schema);

return { name, schema };
}

static generateRecursive<T>(
name: string,
schema: Schema<T>,
subSchemas: { type: string; schema: Schema }[],
): ModelDefinition {
schema = MongoModelFactory.setSchemaOptions(schema);
subSchemas = subSchemas.map(({ type, schema }) => {
schema = MongoModelFactory.setSchemaOptions(schema);

return { type, schema };
});

return {
name,
useFactory: () => {
schema.set('timestamps', true);
schema.set('versionKey', false);
schema,
discriminators: subSchemas.map(({ type, schema }) => {
return { name: type, schema };
}),
};
}

schema.plugin(AutoPopulate);
private static setSchemaOptions(schema: Schema): Schema {
schema.set('versionKey', false);
schema.set('timestamps', true);

return schema;
},
};
schema.pre('save', function (next) {
if ('createdAt' in this) {
this['createdAt'] = convertToKST(new Date());
}

this['updatedAt'] = convertToKST(new Date());

next();
});

schema.plugin(AutoPopulate);

return schema;
}
}
1 change: 1 addition & 0 deletions src/common/database/redis/redis.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DynamicModule, Module } from '@nestjs/common';

import { RedisService } from './service';

export interface RedisModuleOptions {
Expand Down
30 changes: 20 additions & 10 deletions src/common/database/redis/service/redis.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,33 @@ export class RedisService {
}

async exists(key: string): Promise<boolean> {
if (!this.group) throw new Error('Group is not set');
if (!this.group) {
throw new Error('Group is not set');
}

const _key = this.#generateKey(key);
const _key = this.generateKey(key);

const exists = await this.redisClient.exists(_key);

return exists === 1;
}

async get(key: string): Promise<string> {
if (!this.group) throw new Error('Group is not set');
if (!this.group) {
throw new Error('Group is not set');
}

const _key = this.#generateKey(key);
const _key = this.generateKey(key);

return (await this.redisClient.get(_key)) || '';
}

async set(key: string, value: string, seconds: number = 0): Promise<void> {
if (!this.group) throw new Error('Group is not set');
if (!this.group) {
throw new Error('Group is not set');
}

const _key = this.#generateKey(key);
const _key = this.generateKey(key);

let event: RedisSetEvent | RedisSetTtlEvent;

Expand All @@ -63,17 +69,21 @@ export class RedisService {
}

async delete(key: string): Promise<void> {
if (!this.group) throw new Error('Group is not set');
if (!this.group) {
throw new Error('Group is not set');
}

const _key = this.#generateKey(key);
const _key = this.generateKey(key);

await this.redisClient.del(_key);

this.eventEmitter.emit(RedisDeleteEvent.EVENT_NAME, new RedisDeleteEvent(_key));
}

#generateKey(key: string): string {
if (!this.group) throw new Error('Group is not set');
private generateKey(key: string): string {
if (!this.group) {
throw new Error('Group is not set');
}

return `${this.group}:${key}`;
}
Expand Down
1 change: 1 addition & 0 deletions src/common/http/filter/exception.filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
HttpStatus,
NotFoundException,
} from '@nestjs/common';

import { ApiException } from '@wink/swagger';

@Catch(ApiException)
Expand Down
2 changes: 1 addition & 1 deletion src/common/http/interceptor/api-response.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { ApiException } from '@wink/swagger';

import { Response } from 'express';
import { catchError, map, Observable, throwError } from 'rxjs';
import { Observable, catchError, map, throwError } from 'rxjs';

@Injectable()
export class ApiResponseInterceptor implements NestInterceptor {
Expand Down
Loading

0 comments on commit 0ff5531

Please sign in to comment.