QnoteAI์ ๋ฐฑ์๋ ์๋ฒ๋ NestJS๋ก ๊ตฌ์ถ๋ RESTful API ์๋ฒ์
๋๋ค.
์ฃผ์ ๊ธฐ๋ฅ์ ์ฌ์ฉ์ ์ธ์ฆ, ์ผ๊ธฐ/ํ๊ทธ/์ค์ผ์ค ๊ด๋ฆฌ, AI ๊ธฐ๋ฐ ์ฑ, ๊ทธ๋ฆฌ๊ณ ๋ค์ํ ๋ฐ์ดํฐ ์ํฐํฐ ๊ด๋ฆฌ์
๋๋ค.
/srcmain.ts: ์ฑ ๋ถํธ์คํธ๋ฉ, ๊ธ๋ก๋ฒ ํ์ดํ, Swagger ๋ฌธ์ ์๋ํapp.module.ts: ์ ์ญ ๋ชจ๋, TypeORM/Config ๋ชจ๋ ๋ฐ ์ฃผ์ feature module ๋ฑ๋กusers/,diaries/,tags/,chat-messages/,chat-sessions/,schedules/,user-passwords/: ๊ฐ ๋๋ฉ์ธ๋ณ ๋ชจ๋ ๋ฐ ์ํฐํฐ
- ๋์ปค ๋ฐ ํ๊ฒฝ์ค์
docker-compose.*.yaml,dockerfile.*,.env๋ฑ
๋ ๋ง์ ๊ตฌ์กฐ๋ ํด๋ ๊ตฌ์กฐ ๋ฐ๋ก๋ณด๊ธฐ์์ ํ์ธํ ์ ์์ต๋๋ค.
- Framework: NestJS
- Database: MySQL (TypeORM ์ฌ์ฉ)
- ํ๊ฒฝ๋ณ์: dotenv ๋ฐ ConfigModule์ ํตํ ๊ด๋ฆฌ
- Swagger: API ๋ฌธ์ ์๋ ์์ฑ ๋ฐ ์ ๊ณต (
/api์๋ํฌ์ธํธ) - ํ ์คํธ: Jest
TypeORM์ app.module.ts์์ ์๋์ ๊ฐ์ด ์ค์ ๋ฉ๋๋ค.
- ํ๊ฒฝ๋ณ์ ๊ธฐ๋ฐ์ผ๋ก DB ์ ๋ณด ์ฃผ์
- ์ํฐํฐ ์๋ ๋ก๋:
entities: [__dirname + '/**/*.entity{.ts,.js}'] - ๋ง์ด๊ทธ๋ ์ด์
๋ฐ ๋๊ธฐํ ์ง์ (
synchronize: true, ๊ฐ๋ฐํ๊ฒฝ์์๋ง ๊ถ์ฅ) - ์์:
TypeOrmModule.forRoot({ type: 'mysql', host: process.env.DB_DOCKER_NAME, port: parseInt(process.env.DB_PORT!), username: 'root', password: process.env.DB_ROOT_PW, database: process.env.DB_NAME, entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: true, })
- Swagger ๊ธฐ๋ฐ์ ์๋ ๋ฌธ์ํ(
localhost:3000/api) - JWT Bearer ์ธ์ฆ ๋ฐฉ์ ์ง์
- ์์กด์ฑ ์ค์น
npm install
- ํ๊ฒฝ๋ณ์(.env) ์ค์
- ์๋ฒ ์คํ
npm run start:dev
- API ๋ฌธ์ ๋ณด๊ธฐ
- ์ฝ๋ ์คํ์ผ: ESLint, Prettier ์ ์ฉ
- Docker/Docker Compose ์ง์
- ํ
์คํธ ์ฝ๋:
/test๋๋ ํ ๋ฆฌ
๋๋ถ๋ถ์ ์๋ํฌ์ธํธ๋ JWT ์ธ์ฆ์ด ํ์ํ๋ฉฐ, Swagger ๋ฌธ์(/api)์์ ์์ธ ์คํ์ ํ์ธํ ์ ์์ต๋๋ค.
POST /auth/login: ์ด๋ฉ์ผ+๋น๋ฐ๋ฒํธ ๋ก๊ทธ์ธ (Access/Refresh Token ๋ฐ๊ธ)POST /auth/restore: refreshToken์ผ๋ก accessToken ์ฌ๋ฐ๊ธPOST /auth/oauth: OAuth ๋ก๊ทธ์ธ (Google ๋ฑ)GET /auth/profile: ๋ด ํ๋กํ ์ ๋ณด ์กฐํ (JWT ํ์)POST /auth/register: ํ์๊ฐ์
GET /users: ์ ์ฒด ์ ์ ๋ชฉ๋ก ์กฐํ (๊ด๋ฆฌ์์ฉ)GET /users/my: ๋ด ์ ๋ณด ์กฐํ (JWT ํ์)
POST /diaries: ์ผ๊ธฐ ์์ฑGET /diaries: ๋ด ์ผ๊ธฐ ๋ชฉ๋ก ์กฐํ (ํ์ด์ง)GET /diaries/recent: ์ต๊ทผ N๊ฐ์ ์ผ๊ธฐ ์กฐํGET /diaries/recent/one: ๊ฐ์ฅ ์ต๊ทผ ์ผ๊ธฐ ์กฐํGET /diaries/:id: ํน์ ์ผ๊ธฐ ์์ธ ์กฐํPUT /diaries/:id: ์ผ๊ธฐ ์์ DELETE /diaries/:id: ์ผ๊ธฐ ์ญ์
GET /schedules: ๋ด ์ผ์ ์ ์ฒด ์กฐํGET /schedules/:id: ์ผ์ ๋จ๊ฑด ์กฐํPOST /schedules: ์ผ์ ์์ฑPUT /schedules/:id: ์ผ์ ์์ DELETE /schedules/:id: ์ผ์ ์ญ์
POST /openai/send-message: ์ฑ๋ด(AI)์๊ฒ ๋ฉ์์ง ์ ์ก, ๋ต๋ณ ๋ฐ๊ธฐPOST /openai/metadata: ์ผ๊ธฐ ๋ด์ฉ ๊ธฐ๋ฐ ๋ฉํ๋ฐ์ดํฐ(๊ฐ์ ๋ฑ) ์ถ์ถPOST /openai/summary/content: ์ผ๊ธฐ ์์ฝ ์์ฑGET /openai/predict/recent: ์ต๊ทผ ์ธ์ ๊ธฐ๋ฐ ์์ธก ์๋ต
GET /sessions/recent: ์ต๊ทผ ์ฑ ์ธ์ ๋ชฉ๋ก ์กฐํ
- ๋ฐฉ์: JWT ๊ธฐ๋ฐ (Access, Refresh Token), Passport.js(Local, JWT), OAuth(Google ๋ฑ)
- AccessToken: 3์๊ฐ ์ ํจ, Bearer ๋ฐฉ์์ผ๋ก ์ ๋ฌ
- RefreshToken: 7์ผ ์ ํจ, accessToken ์ฌ๋ฐ๊ธ์ ์ฌ์ฉ
- ๋น๋ฐ๋ฒํธ ์ํธํ: bcrypt ์ฌ์ฉ, SALT ์ ์ฉ
- ์ฃผ์ ๋ก์ง
- ํ์๊ฐ์ /๋ก๊ทธ์ธ ์ ๋น๋ฐ๋ฒํธ ๊ฒ์ฆ ๋ฐ JWT ํ ํฐ ๋ฐ๊ธ
- ๋ชจ๋ ์ธ์ฆ์ด ํ์ํ API์์ JwtAuthGuard ์ ์ฉ
- ํ ํฐ ๋ง๋ฃ/์ค๋ฅ์ ์ ์ ํ ์๋ฌ ๋ฉ์์ง ๋ฐํ
- OAuth ๋ก๊ทธ์ธ(๊ตฌ๊ธ ๋ฑ) ์ง์, ์ธ๋ถ ํ ํฐ ๊ฒ์ฆ ํ ์์ฒด ํ ํฐ ๋ฐ๊ธ
// Auth Module (์ผ๋ถ ์์)
JwtModule.register({
secret: process.env.JWT_SECRET_KEY,
signOptions: { expiresIn: '3h' },
});// JWT Strategy (์ผ๋ถ ์์)
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET_KEY!,
});- ๊ด๋ จ ํ์ผ
src/auth/auth.module.tssrc/auth/auth.service.tssrc/auth/jwt.strategy.tssrc/users/user.entity.ts,src/user-passwords/user-password.entity.ts
- Swagger API ๋ฌธ์
- ์ํฐํฐ ๋ฐ ์๋น์ค ์ฝ๋ ์ ์ฒด ๋ณด๊ธฐ
- ๋ ๋ง์ ์๋ํฌ์ธํธ/์์ธ ์ฝ๋๋
/src/*/*.controller.ts,/src/auth/๋ฑ์์ ํ์ธ ๊ฐ๋ฅ - ํด๋ ๊ตฌ์กฐ ๋ฐ ํ์ผ ์ ์ฒด ๋ณด๊ธฐ
- ์ํฐํฐ ์ฝ๋ ์ ์ฒด ๋ณด๊ธฐ
๋ณธ ์์ฝ์ ๋ํ API์ ์ธ์ฆ ๋ก์ง์ ์ค์ฌ์ผ๋ก ์ ๋ฆฌ๋์์ต๋๋ค.
๋ ๋ค์ํ ๊ธฐ๋ฅ ๋ฐ ์ธ๋ถ ๊ตฌํ์ ์ฝ๋์ Swagger ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์.
