Repository: jis-kim/essay-evaluation
Pull request #15: feat: initialize Prisma integration and configuration management
Author: login: jis-kim
Labels: name: enhancement
description: New feature or request
Created: 2025-04-26T07:39:55Z | Merged: 2025-04-26T08:03:43Z

## Summary
configuration 관리(@nestjs/config, zod) 및 Prisma를 이용한 DB 통합 초기 설정.

## Describe your changes
- 환경 변수 관리 및 검증 기능 추가 (AppConfigModule).
- Prisma ORM을 이용한 데이터베이스 연동 (PrismaModule, PrismaService).
  - 초기 데이터 설정을 위한 시딩 스크립트 추가 (seed.ts).
  - 공통 레포지토리 인터페이스 (BaseRepository) 도입.

## Issue number and link
- close #2

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **신규 기능**
    - 환경 변수 예시 파일이 추가되어 애플리케이션 설정이 쉬워졌습니다.
    - Prisma 데이터베이스 시드 스크립트가 도입되어 초기 학생 데이터를 자동으로 생성할 수 있습니다.
    - 공통 저장소 인터페이스 및 다양한 환경 설정, 검증, 서비스 모듈이 추가되어 설정 관리가 통합되었습니다.
    - Prisma 및 환경설정 관련 글로벌 모듈과 서비스가 도입되어 전체 앱에서 쉽게 사용할 수 있습니다.

- **의존성 추가**
    - 환경 변수 관리 및 검증을 위한 외부 패키지들이 추가되었습니다.

- **스타일**
    - 콘솔 사용 제한 규칙이 ESLint 설정에서 제거되었습니다.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Linked issue #2: ✨ [Feature] Prisma 설정 및 데이터베이스 스키마 정의
### Description

- 스키마 정의
- 마이그레이션

### Todo

_No response_

### ETC

_No response_

Diff:
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..240d6b4
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,25 @@
+NODE_ENV=production
+PORT=3000
+
+# chat completion information
+AZURE_ENDPOINT_URL=
+AZURE_ENDPOINT_KEY=
+OPENAI_API_VERSION=
+AZURE_OPENAI_DEPLOYMENT_NAME=
+
+# blob storage
+AZURE_ACCOUNT_KEY=
+AZURE_ACCOUNT_NAME=
+AZURE_CONNECTION_STRING=
+AZURE_CONTAINER=
+
+
+DATABASE_URL=
+
+POSTGRES_USER=
+POSTGRES_PASSWORD=
+POSTGRES_DB=
+POSTGRES_PORT=
+
+JWT_SECRET=
+JWT_EXPIRES_IN=
diff --git a/.env.sample b/.env.sample
deleted file mode 100644
index e69de29..0000000
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 54b074e..420dac9 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -59,7 +59,6 @@ export default tseslint.config(
       '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
       '@typescript-eslint/no-explicit-any': 'error',
       '@typescript-eslint/no-non-null-assertion': 'error',
-      'no-console': ['error', { allow: ['warn', 'error'] }],
 
       // 타입스크립트 유용한 규칙
       '@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }],
@@ -99,7 +98,7 @@ export default tseslint.config(
       'no-eval': 'error',
       'no-implied-eval': 'error',
     },
-  },
+},
   // 테스트 파일 관련 설정
   {
     files: ['**/*.spec.ts', '**/*.test.ts'],
diff --git a/package.json b/package.json
index 3f0420a..12fd8d6 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
   "dependencies": {
     "@azure/storage-blob": "^12.27.0",
     "@nestjs/common": "^11.0.1",
+    "@nestjs/config": "^4.0.2",
     "@nestjs/core": "^11.0.1",
     "@nestjs/jwt": "^11.0.0",
     "@nestjs/passport": "^11.0.5",
@@ -36,7 +37,8 @@
     "passport": "^0.7.0",
     "passport-jwt": "^4.0.1",
     "reflect-metadata": "^0.2.2",
-    "rxjs": "^7.8.1"
+    "rxjs": "^7.8.1",
+    "zod": "^3.24.3"
   },
   "devDependencies": {
     "@eslint/eslintrc": "^3.2.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1ac877f..2abb84d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -14,6 +14,9 @@ importers:
       '@nestjs/common':
         specifier: ^11.0.1
         version: 11.1.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/config':
+        specifier: ^4.0.2
+        version: 4.0.2(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)
       '@nestjs/core':
         specifier: ^11.0.1
         version: 11.1.0(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.0)(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -37,7 +40,7 @@ importers:
         version: 0.14.1
       openai:
         specifier: ^4.96.0
-        version: 4.96.0
+        version: 4.96.0(zod@3.24.3)
       passport:
         specifier: ^0.7.0
         version: 0.7.0
@@ -50,6 +53,9 @@ importers:
       rxjs:
         specifier: ^7.8.1
         version: 7.8.2
+      zod:
+        specifier: ^3.24.3
+        version: 3.24.3
     devDependencies:
       '@eslint/eslintrc':
         specifier: ^3.2.0
@@ -963,6 +969,12 @@ packages:
       class-validator:
         optional: true
 
+  '@nestjs/config@4.0.2':
+    resolution: {integrity: sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==}
+    peerDependencies:
+      '@nestjs/common': ^10.0.0 || ^11.0.0
+      rxjs: ^7.1.0
+
   '@nestjs/core@11.1.0':
     resolution: {integrity: sha512-IeXbTRPrr6xAVbETlDE+miSkNmYf/cPhCa9GU9gFtPO6pVNuAeG/dNrjLVc23mJtUlT/ibdsoW35TlSyHLkzEA==}
     engines: {node: '>= 20'}
@@ -1992,6 +2004,14 @@ packages:
     resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
     engines: {node: '>=0.10.0'}
 
+  dotenv-expand@12.0.1:
+    resolution: {integrity: sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==}
+    engines: {node: '>=12'}
+
+  dotenv@16.4.7:
+    resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
+    engines: {node: '>=12'}
+
   dunder-proto@1.0.1:
     resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
     engines: {node: '>= 0.4'}
@@ -4214,6 +4234,9 @@ packages:
     resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==}
     engines: {node: '>=18'}
 
+  zod@3.24.3:
+    resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==}
+
 snapshots:
 
   '@ampproject/remapping@2.3.0':
@@ -5145,6 +5168,14 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@nestjs/config@4.0.2(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)':
+    dependencies:
+      '@nestjs/common': 11.1.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      dotenv: 16.4.7
+      dotenv-expand: 12.0.1
+      lodash: 4.17.21
+      rxjs: 7.8.2
+
   '@nestjs/core@11.1.0(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.0)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
     dependencies:
       '@nestjs/common': 11.1.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -6286,6 +6317,12 @@ snapshots:
     dependencies:
       esutils: 2.0.3
 
+  dotenv-expand@12.0.1:
+    dependencies:
+      dotenv: 16.4.7
+
+  dotenv@16.4.7: {}
+
   dunder-proto@1.0.1:
     dependencies:
       call-bind-apply-helpers: 1.0.2
@@ -7883,7 +7920,7 @@ snapshots:
     dependencies:
       mimic-fn: 2.1.0
 
-  openai@4.96.0:
+  openai@4.96.0(zod@3.24.3):
     dependencies:
       '@types/node': 18.19.86
       '@types/node-fetch': 2.6.12
@@ -7892,6 +7929,8 @@ snapshots:
       form-data-encoder: 1.7.2
       formdata-node: 4.4.1
       node-fetch: 2.7.0
+    optionalDependencies:
+      zod: 3.24.3
     transitivePeerDependencies:
       - encoding
 
@@ -8895,3 +8934,5 @@ snapshots:
   yocto-queue@0.1.0: {}
 
   yoctocolors-cjs@2.1.2: {}
+
+  zod@3.24.3: {}
diff --git a/prisma/seed.ts b/prisma/seed.ts
new file mode 100644
index 0000000..3679c46
--- /dev/null
+++ b/prisma/seed.ts
@@ -0,0 +1,48 @@
+import { PrismaClient } from '@prisma/client';
+
+const prisma = new PrismaClient();
+
+const createStudents = async (students: { studentName: string }[]): Promise<void> => {
+  await prisma.student.createMany({
+    data: students,
+  });
+};
+
+async function main(): Promise<void> {
+  // check if the table is empty
+  const studentCount = await prisma.student.count();
+  if (studentCount > 0) {
+    console.log('Student table is not empty, skipping seeding');
+    return;
+  }
+
+  // 학생 테스트 데이터 생성
+  const students = [
+    {
+      studentName: '김민준',
+    },
+    {
+      studentName: '이서연',
+    },
+    {
+      studentName: '박지우',
+    },
+    {
+      studentName: '최현우',
+    },
+    {
+      studentName: '정수아',
+    },
+  ];
+
+  await createStudents(students);
+}
+
+main()
+  .catch((e) => {
+    console.error(e);
+    process.exit(1);
+  })
+  .finally(() => {
+    void prisma.$disconnect();
+  });
diff --git a/src/app.module.ts b/src/app.module.ts
index 6a9bc16..fa9ae0e 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -2,9 +2,11 @@ import { Module } from '@nestjs/common';
 
 import { AppController } from './app.controller';
 import { AppService } from './app.service';
+import { AppConfigModule } from './config/config.module';
+import { PrismaModule } from './prisma/prisma.module';
 
 @Module({
-  imports: [],
+  imports: [AppConfigModule, PrismaModule],
   controllers: [AppController],
   providers: [AppService],
 })
diff --git a/src/common/interfaces/base.repository.interface.ts b/src/common/interfaces/base.repository.interface.ts
new file mode 100644
index 0000000..565b191
--- /dev/null
+++ b/src/common/interfaces/base.repository.interface.ts
@@ -0,0 +1,7 @@
+export interface BaseRepository<T> {
+  create(data: Partial<T>): Promise<T>;
+  findById(id: string): Promise<T | null>;
+  findAll(): Promise<T[]>;
+  update(id: string, data: Partial<T>): Promise<T>;
+  delete(id: string): Promise<T>;
+}
diff --git a/src/config/config.module.ts b/src/config/config.module.ts
new file mode 100644
index 0000000..b514904
--- /dev/null
+++ b/src/config/config.module.ts
@@ -0,0 +1,28 @@
+import { Global, Module } from '@nestjs/common';
+import { ConfigModule } from '@nestjs/config';
+
+import { AppConfigService } from './config.service';
+import { databaseConfig, azureOpenAIConfig, azureBlobConfig, jwtConfig, appConfig } from './configuration';
+import { envSchema } from './env.validation';
+
+@Global()
+@Module({
+  imports: [
+    ConfigModule.forRoot({
+      validate: (config: Record<string, unknown>) => {
+        const result = envSchema.safeParse(config);
+        if (!result.success) {
+          console.error('❌ Invalid environment variables:', result.error.format());
+          throw new Error('Invalid environment variables');
+        }
+        return result.data;
+      },
+      //isGlobal: true,
+      cache: true,
+      load: [databaseConfig, azureOpenAIConfig, azureBlobConfig, jwtConfig, appConfig],
+    }),
+  ],
+  providers: [AppConfigService],
+  exports: [AppConfigService],
+})
+export class AppConfigModule {}
diff --git a/src/config/config.service.ts b/src/config/config.service.ts
new file mode 100644
index 0000000..b91f390
--- /dev/null
+++ b/src/config/config.service.ts
@@ -0,0 +1,94 @@
+import { Injectable } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+
+export interface DatabaseConfig {
+  user: string;
+  password: string;
+  dbName: string;
+  port: number;
+  url: string;
+}
+
+export interface AzureOpenAIConfig {
+  endpointUrl: string;
+  apiKey: string;
+  apiVersion: string;
+  deploymentName: string;
+}
+
+export interface AzureBlobConfig {
+  accountName: string;
+  accountKey: string;
+  connectionString: string;
+  container: string;
+}
+
+export interface JwtConfig {
+  secret: string;
+  expiresIn: string;
+}
+
+@Injectable()
+export class AppConfigService {
+  constructor(private readonly configService: ConfigService) {}
+
+  get nodeEnv(): string {
+    return this.configService.get<string>('app.nodeEnv') ?? 'development';
+  }
+
+  get port(): number {
+    return this.configService.get<number>('app.port') ?? 3000;
+  }
+
+  // 데이터베이스 설정
+  get databaseConfig(): DatabaseConfig {
+    return {
+      user: this.configService.getOrThrow<string>('database.user'),
+      password: this.configService.getOrThrow<string>('database.password'),
+      dbName: this.configService.getOrThrow<string>('database.dbName'),
+      port: this.configService.get<number>('database.port') ?? 5432,
+      url: this.configService.getOrThrow<string>('database.url'),
+    };
+  }
+
+  // Azure OpenAI 설정
+  get azureOpenAIConfig(): AzureOpenAIConfig {
+    return {
+      endpointUrl: this.configService.getOrThrow<string>('azureOpenAI.endpointUrl'),
+      apiKey: this.configService.getOrThrow<string>('azureOpenAI.apiKey'),
+      apiVersion: this.configService.getOrThrow<string>('azureOpenAI.apiVersion'),
+      deploymentName: this.configService.getOrThrow<string>('azureOpenAI.deploymentName'),
+    };
+  }
+
+  // Azure Blob Storage 설정
+  get azureBlobConfig(): AzureBlobConfig {
+    return {
+      accountName: this.configService.getOrThrow<string>('azureBlob.accountName'),
+      accountKey: this.configService.getOrThrow<string>('azureBlob.accountKey'),
+      connectionString: this.configService.getOrThrow<string>('azureBlob.connectionString'),
+      container: this.configService.getOrThrow<string>('azureBlob.container'),
+    };
+  }
+
+  // JWT 설정
+  get jwtConfig(): JwtConfig {
+    return {
+      secret: this.configService.getOrThrow<string>('jwt.secret'),
+      expiresIn: this.configService.getOrThrow<string>('jwt.expiresIn'),
+    };
+  }
+
+  // 환경 체크 헬퍼 메서드
+  get isDevelopment(): boolean {
+    return this.nodeEnv === 'development';
+  }
+
+  get isProduction(): boolean {
+    return this.nodeEnv === 'production';
+  }
+
+  get isTest(): boolean {
+    return this.nodeEnv === 'test';
+  }
+}
diff --git a/src/config/configuration.ts b/src/config/configuration.ts
new file mode 100644
index 0000000..5c7ec31
--- /dev/null
+++ b/src/config/configuration.ts
@@ -0,0 +1,39 @@
+import { registerAs } from '@nestjs/config';
+
+// 데이터베이스 설정
+export const databaseConfig = registerAs('database', () => ({
+  user: process.env.POSTGRES_USER,
+  password: process.env.POSTGRES_PASSWORD,
+  dbName: process.env.POSTGRES_DB,
+  port: parseInt(process.env.POSTGRES_PORT || '5432', 10),
+  url: process.env.DATABASE_URL,
+}));
+
+// JWT 설정
+export const jwtConfig = registerAs('jwt', () => ({
+  secret: process.env.JWT_SECRET,
+  expiresIn: process.env.JWT_EXPIRES_IN || '30d',
+}));
+
+// Azure OpenAI 설정
+export const azureOpenAIConfig = registerAs('azureOpenAI', () => ({
+  endpointUrl: process.env.AZURE_ENDPOINT_URL,
+  apiKey: process.env.AZURE_ENDPOINT_KEY,
+  apiVersion: process.env.OPENAI_API_VERSION,
+  deploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME,
+}));
+
+// Azure Blob Storage 설정
+export const azureBlobConfig = registerAs('azureBlob', () => ({
+  accountName: process.env.AZURE_ACCOUNT_NAME,
+  accountKey: process.env.AZURE_ACCOUNT_KEY,
+  connectionString: process.env.AZURE_CONNECTION_STRING,
+  container: process.env.AZURE_CONTAINER,
+}));
+
+// 애플리케이션 설정
+export const appConfig = registerAs('app', () => ({
+  nodeEnv: process.env.NODE_ENV,
+  port: parseInt(process.env.PORT || '3000', 10),
+  name: 'essay-evaluation-service',
+}));
diff --git a/src/config/env.validation.ts b/src/config/env.validation.ts
new file mode 100644
index 0000000..bc68090
--- /dev/null
+++ b/src/config/env.validation.ts
@@ -0,0 +1,34 @@
+import { z } from 'zod';
+
+// 포트 번호 검증을 위한 커스텀 스키마
+const portSchema = z.coerce.number().int().min(1024).max(65535);
+
+export const envSchema = z.object({
+  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
+  PORT: portSchema.default(3000),
+
+  // Database
+  POSTGRES_USER: z.string(),
+  POSTGRES_PASSWORD: z.string(),
+  POSTGRES_DB: z.string(),
+  POSTGRES_PORT: portSchema.default(5432),
+  DATABASE_URL: z.string().url(),
+
+  // Azure OpenAI
+  AZURE_ENDPOINT_URL: z.string().url(),
+  AZURE_ENDPOINT_KEY: z.string(),
+  OPENAI_API_VERSION: z.string(),
+  AZURE_OPENAI_DEPLOYMENT_NAME: z.string(),
+
+  // Azure Blob Storage
+  AZURE_ACCOUNT_NAME: z.string(),
+  AZURE_ACCOUNT_KEY: z.string(),
+  AZURE_CONNECTION_STRING: z.string(),
+  AZURE_CONTAINER: z.string(),
+
+  // JWT
+  JWT_SECRET: z.string(),
+  JWT_EXPIRES_IN: z.string().default('30d'),
+});
+
+export type Env = z.infer<typeof envSchema>;
diff --git a/src/prisma/prisma.module.ts b/src/prisma/prisma.module.ts
new file mode 100644
index 0000000..d30ce68
--- /dev/null
+++ b/src/prisma/prisma.module.ts
@@ -0,0 +1,10 @@
+import { Global, Module } from '@nestjs/common';
+
+import { PrismaService } from './prisma.service';
+
+@Global()
+@Module({
+  providers: [PrismaService],
+  exports: [PrismaService],
+})
+export class PrismaModule {}
diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts
new file mode 100644
index 0000000..f8b587d
--- /dev/null
+++ b/src/prisma/prisma.service.ts
@@ -0,0 +1,26 @@
+import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
+import { PrismaClient } from '@prisma/client';
+
+import { AppConfigService } from '../config/config.service';
+
+@Injectable()
+export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
+  constructor(private readonly config: AppConfigService) {
+    super({
+      datasources: {
+        db: {
+          url: config.databaseConfig.url,
+        },
+      },
+      log: config.isDevelopment ? ['query', 'info', 'warn', 'error'] : ['error'],
+    });
+  }
+
+  async onModuleInit(): Promise<void> {
+    await this.$connect();
+  }
+
+  async onModuleDestroy(): Promise<void> {
+    await this.$disconnect();
+  }
+}


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR sets up initial Prisma integration and configuration management using NestJS, zod-based environment variable validation, and a unified configuration service.  
- Implements Prisma ORM integration with connection lifecycle management.  
- Introduces configuration modules and services for databases, JWT, Azure services, and application settings.  
- Adds a BaseRepository interface and a simple seed script for initial data setup.

### Reviewed Changes

Copilot reviewed 10 out of 13 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| src/prisma/prisma.service.ts | Implements PrismaService extending PrismaClient to manage DB connections using configurations. |
| src/prisma/prisma.module.ts | Registers PrismaService as a global provider. |
| src/config/env.validation.ts | Defines a zod schema for environment variable validation. |
| src/config/configuration.ts | Defines configuration objects for database, JWT, Azure OpenAI, Azure Blob Storage, and app settings. |
| src/config/config.service.ts | Provides a typed configuration service with getters for various environment settings. |
| src/config/config.module.ts | Initializes the global configuration module with env validation, though some configurations are omitted in the load list. |
| src/common/interfaces/base.repository.interface.ts | Defines a base repository interface for common CRUD operations. |
| src/app.module.ts | Imports AppConfigModule and PrismaModule to wire up configuration and DB integration. |
| prisma/seed.ts | Provides a seeding script to insert initial student test data into the database. |
</details>


<details>
<summary>Files not reviewed (3)</summary>

* **.env.example**: Language not supported
* **package.json**: Language not supported
* **pnpm-lock.yaml**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**src/config/config.module.ts:22**
* It appears that jwtConfig and appConfig are defined in configuration.ts but are not included in the load array, which may lead to missing configuration values when accessed via AppConfigService. Consider adding jwtConfig and appConfig to the load array to ensure complete configuration support.
```
load: [databaseConfig, azureOpenAIConfig, azureBlobConfig],
```
</details>


state: COMMENTED
author_association: NONE
submitted_at: 2025-04-26T07:40:18Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>.env.example (1)</summary><blockquote>

`1-26`: _🛠️ Refactor suggestion_

**환경 변수 파일의 문서화 및 구조 개선이 필요합니다.**

환경 변수 예시 파일이 잘 구성되어 있지만, 각 변수의 용도와 가능한 값에 대한 설명이 부족합니다. 또한 production 환경이 기본값으로 설정되어 있어 개발 환경 설정이 어려울 수 있습니다.


각 환경 변수에 대한 상세한 설명과 예시 값을 추가하고, 개발 환경에 적합한 기본값을 설정하는 것이 좋습니다:

```diff
- NODE_ENV=production
+ # 애플리케이션 실행 환경 (development, production, test)
+ NODE_ENV=development
- PORT=3000
+ # 서버 포트 번호
+ PORT=3000

# chat completion information
- AZURE_ENDPOINT_URL=
+ # Azure OpenAI 엔드포인트 URL (예: https://your-resource-name.openai.azure.com)
+ AZURE_ENDPOINT_URL=
- AZURE_ENDPOINT_KEY=
+ # Azure OpenAI API 키
+ AZURE_ENDPOINT_KEY=
- OPEN_API_VERSION=
+ # OpenAI API 버전 (예: 2023-05-15)
+ OPEN_API_VERSION=
- AZURE_OPENAI_DEPLOYMENT_NAME=
+ # Azure OpenAI 배포 이름
+ AZURE_OPENAI_DEPLOYMENT_NAME=

# blob storage
- AZURE_ACCOUNT_KEY=
+ # Azure Storage 계정 키
+ AZURE_ACCOUNT_KEY=
- AZURE_ACCOUNT_NAME=
+ # Azure Storage 계정 이름
+ AZURE_ACCOUNT_NAME=
- AZURE_CONNECTION_STRING=
+ # Azure Storage 연결 문자열
+ AZURE_CONNECTION_STRING=
- AZURE_CONTAINER=
+ # Azure Storage 컨테이너 이름
+ AZURE_CONTAINER=

- 

- DATABASE_URL=
+ # 데이터베이스 연결 URL (예: postgresql://user:password@localhost:5432/dbname)
+ DATABASE_URL=

- POSTGRES_USER=
+ # PostgreSQL 사용자 이름
+ POSTGRES_USER=
- POSTGRES_PASSWORD=
+ # PostgreSQL 비밀번호
+ POSTGRES_PASSWORD=
- POSTGRES_DB=
+ # PostgreSQL 데이터베이스 이름
+ POSTGRES_DB=
- POSTGRES_PORT=
+ # PostgreSQL 포트 (기본값: 5432)
+ POSTGRES_PORT=5432

- JWT_SECRET=
+ # JWT 서명 시크릿 키 (긴 랜덤 문자열 사용 권장)
+ JWT_SECRET=
- JWT_EXPIRES_IN=
+ # JWT 만료 시간 (예: 1h, 1d, 30d)
+ JWT_EXPIRES_IN=30d
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>src/prisma/prisma.module.ts (1)</summary><blockquote>

`1-10`: **Prisma 모듈 구성이 잘 되었습니다만, 문서화를 추가하면 더 좋을 것 같습니다.**

`PrismaModule`이 전역 모듈(`@Global()`)로 잘 설정되어 있어, 애플리케이션 전체에서 `PrismaService`를 주입받아 사용할 수 있습니다. 모듈의 구조는 NestJS 모범 사례를 잘 따르고 있습니다.

한 가지 제안드리고 싶은 점은, 전역 모듈의 경우 해당 모듈의 목적과 사용법에 대한 간단한 JSDoc 주석을 추가하면 코드 가독성과 유지보수성이 더 향상될 것 같습니다.



```diff
+/**
+ * 전역으로 사용 가능한 Prisma ORM 모듈입니다.
+ * 이 모듈은 애플리케이션 전체에서 데이터베이스 접근을 위한 PrismaService를 제공합니다.
+ */
@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/order)

---

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/no-duplicates)

</details>

</details>

</blockquote></details>
<details>
<summary>prisma/seed.ts (2)</summary><blockquote>

`1-39`: **데이터베이스 초기화 스크립트의 효율성 개선이 필요합니다.**

이 시드 스크립트는 기본적인 학생 데이터를 생성하는 기능을 잘 구현했습니다. 그러나 학생 데이터를 한 명씩 생성하는 방식은 대량의 데이터를 처리해야 할 때 비효율적일 수 있습니다.


Prisma의 `createMany` 메서드를 사용하여 여러 학생 레코드를 한 번에 생성하는 것이 더 효율적입니다:

```diff
- for (const student of students) {
-   await prisma.student.create({
-     data: student,
-   });
- }
+ await prisma.student.createMany({
+   data: students,
+ });
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/order)

---

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/no-duplicates)

</details>

</details>

---

`38-38`: **불필요한 void 연산자 사용.**

`void` 연산자는 여기서 불필요합니다. `.finally()` 블록에서는 반환값이 사용되지 않습니다.


```diff
- void prisma.$disconnect();
+ prisma.$disconnect();
```

</blockquote></details>
<details>
<summary>.env.example (1)</summary><blockquote>

`16-16`: **불필요한 빈 줄이 있습니다.**


```diff
- 

```

</blockquote></details>
<details>
<summary>src/config/config.module.ts (1)</summary><blockquote>

`8-28`: **설정 모듈이 잘 구현되었으나 몇 가지 개선 사항이 있습니다.**

환경 변수 검증과 타입 안전성이 잘 구현되어 있습니다. 그러나 몇 가지 개선할 점이 있습니다.



주석 처리된 `isGlobal: true` 옵션은 이미 `@Global()` 데코레이터가 사용되고 있어 중복됩니다. 또한 오류 메시지를 좀 더 유용하게 개선할 수 있습니다:

```diff
@Global()
@Module({
  imports: [
    ConfigModule.forRoot({
      validate: (config: Record<string, unknown>) => {
        const result = envSchema.safeParse(config);
        if (!result.success) {
-          console.error('❌ Invalid environment variables:', result.error.format());
-          throw new Error('Invalid environment variables');
+          console.error('❌ 환경 변수 검증 실패:', result.error.format());
+          throw new Error('환경 변수가 올바르지 않습니다. .env.example 파일을 참고하여 필요한 환경 변수를 설정해주세요.');
        }
        return result.data;
      },
-      //isGlobal: true,
      cache: true,
      load: [databaseConfig, azureOpenAIConfig, azureBlobConfig],
    }),
  ],
  providers: [AppConfigService],
  exports: [AppConfigService],
})
export class AppConfigModule {}
```

</blockquote></details>
<details>
<summary>src/common/interfaces/base.repository.interface.ts (1)</summary><blockquote>

`1-7`: **인터페이스 메서드에 대한 문서화가 없습니다.**

각 메서드의 매개변수와 반환 타입에 대한 설명이 없어 구현체 작성자가 어려움을 겪을 수 있습니다.


JSDoc 형식의 주석을 추가하여 각 메서드의 용도와 매개변수, 반환 타입을 설명하는 것이 좋습니다:

```diff
export interface BaseRepository<T> {
+  /**
+   * 새로운 엔티티를 생성합니다.
+   * @param data 생성할 엔티티 데이터
+   * @returns 생성된 엔티티
+   */
  create(data: Partial<T>): Promise<T>;
  
+  /**
+   * ID로 엔티티를 조회합니다.
+   * @param id 조회할 엔티티의 ID
+   * @returns 찾은 엔티티 또는 null
+   */
  findById(id: string): Promise<T | null>;
  
+  /**
+   * 모든 엔티티를 조회합니다.
+   * @returns 엔티티 배열
+   */
  findAll(): Promise<T[]>;
  
+  /**
+   * 엔티티를 수정합니다.
+   * @param id 수정할 엔티티의 ID
+   * @param data 수정할 데이터
+   * @returns 수정된 엔티티
+   */
  update(id: string, data: Partial<T>): Promise<T>;
  
+  /**
+   * 엔티티를 삭제합니다.
+   * @param id 삭제할 엔티티의 ID
+   * @returns 삭제된 엔티티
+   */
  delete(id: string): Promise<T>;
}
```

</blockquote></details>
<details>
<summary>src/config/configuration.ts (1)</summary><blockquote>

`34-39`: **애플리케이션 이름을 하드코딩하지 않는 것이 좋습니다.**

애플리케이션 이름이 하드코딩되어 있습니다. 설정 값은 가능한 환경 변수로 관리하는 것이 좋으며, 기본값을 제공하는 형태로 변경하는 것이 바람직합니다.


```diff
export const appConfig = registerAs('app', () => ({
  nodeEnv: process.env.NODE_ENV,
  port: parseInt(process.env.PORT || '3000', 10),
- name: 'essay-evaluation-service',
+ name: process.env.APP_NAME || 'essay-evaluation-service',
}));
```

</blockquote></details>
<details>
<summary>src/config/config.service.ts (1)</summary><blockquote>

`55-62`: **Azure OpenAI 설정에 대한 문서 주석이 도움이 됩니다.**

각 설정 접근자에 대한 한글 주석이 코드 이해에 도움이 됩니다. 다만, 더 구체적인 정보(예: 각 설정 값의 형식이나 사용 목적)를 포함하면 더 좋을 것 같습니다.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 25f9072e1595b6e8e6cd4890b27f21e112f50706 and b805f2065a93e950496572940c36e0a1db173d37.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`

</details>

<details>
<summary>📒 Files selected for processing (11)</summary>

* `.env.example` (1 hunks)
* `package.json` (2 hunks)
* `prisma/seed.ts` (1 hunks)
* `src/app.module.ts` (1 hunks)
* `src/common/interfaces/base.repository.interface.ts` (1 hunks)
* `src/config/config.module.ts` (1 hunks)
* `src/config/config.service.ts` (1 hunks)
* `src/config/configuration.ts` (1 hunks)
* `src/config/env.validation.ts` (1 hunks)
* `src/prisma/prisma.module.ts` (1 hunks)
* `src/prisma/prisma.service.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (3)</summary>

<details>
<summary>src/prisma/prisma.module.ts (1)</summary><blockquote>

<details>
<summary>src/app.module.ts (1)</summary>

* `Module` (8-13)

</details>

</blockquote></details>
<details>
<summary>src/config/config.module.ts (4)</summary><blockquote>

<details>
<summary>src/app.module.ts (1)</summary>

* `Module` (8-13)

</details>
<details>
<summary>src/config/env.validation.ts (1)</summary>

* `envSchema` (6-32)

</details>
<details>
<summary>src/config/config.service.ts (3)</summary>

* `databaseConfig` (44-52)
* `azureOpenAIConfig` (55-62)
* `azureBlobConfig` (65-72)

</details>
<details>
<summary>src/config/configuration.ts (3)</summary>

* `databaseConfig` (4-10)
* `azureOpenAIConfig` (19-24)
* `azureBlobConfig` (27-32)

</details>

</blockquote></details>
<details>
<summary>src/config/configuration.ts (1)</summary><blockquote>

<details>
<summary>src/config/config.service.ts (4)</summary>

* `databaseConfig` (44-52)
* `jwtConfig` (75-80)
* `azureOpenAIConfig` (55-62)
* `azureBlobConfig` (65-72)

</details>

</blockquote></details>

</details><details>
<summary>🪛 ESLint</summary>

<details>
<summary>src/prisma/prisma.module.ts</summary>

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/order)

---

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/no-duplicates)

</details>
<details>
<summary>src/prisma/prisma.service.ts</summary>

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/order)

---

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/no-duplicates)

</details>
<details>
<summary>prisma/seed.ts</summary>

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/order)

---

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/no-duplicates)

</details>
<details>
<summary>src/config/config.module.ts</summary>

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/order)

---

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/no-duplicates)

</details>
<details>
<summary>src/config/env.validation.ts</summary>

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/order)

---

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/no-duplicates)

</details>
<details>
<summary>src/config/configuration.ts</summary>

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/order)

---

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/no-duplicates)

</details>
<details>
<summary>src/config/config.service.ts</summary>

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/order)

---

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/no-duplicates)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (11)</summary><blockquote>

<details>
<summary>package.json (1)</summary>

`28-28`: **환경 설정 관리를 위한 적절한 의존성 추가.**

환경 변수 관리 및 검증을 위해 필요한 패키지를 적절하게 추가하셨습니다. `@nestjs/config`는 NestJS에서 공식적으로 지원하는 환경 설정 모듈이며, `zod`는 타입 안전성이 뛰어난 스키마 검증 라이브러리입니다. 이 두 패키지의 조합은 애플리케이션의 환경 설정을 안전하게 관리하는 데 매우 효과적입니다.



Also applies to: 40-41

</details>
<details>
<summary>src/config/env.validation.ts (3)</summary>

`1-5`: **포트 번호를 위한 커스텀 검증 스키마 구현이 잘 되었습니다.**

`zod` 라이브러리를 사용하여 포트 번호 검증을 위한 커스텀 스키마를 구현하셨습니다. 입력값을 숫자로 변환하고, 정수이면서 사용자 포트 범위(1024-65535) 내에 있는지 검증하는 로직이 잘 구현되었습니다.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/order)

---

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/no-duplicates)

</details>

</details>

---

`6-32`: **환경 변수 검증 스키마가 체계적으로 구성되었습니다.**

환경 변수 검증 스키마가 카테고리별로 잘 구성되어 있습니다:
- 기본 서버 설정(NODE_ENV, PORT)
- 데이터베이스 설정
- Azure OpenAI 설정
- Azure Blob Storage 설정
- JWT 인증 설정

필요한 경우 기본값을 설정하고, URL과 같은 특수한 형식에 대한 검증을 추가한 점이 좋습니다. 특히 `NODE_ENV`와 `PORT`에 기본값을 제공하여 개발 환경에서 쉽게 사용할 수 있게 한 점이 실용적입니다.

---

`34-34`: **타입 안전성을 위한 환경 변수 타입 정의가 좋습니다.**

zod 스키마에서 자동으로 타입을 유추하여 `Env` 타입을 정의하셨습니다. 이를 통해 애플리케이션 전체에서 환경 변수를 타입 안전하게 사용할 수 있습니다. 이는 타입스크립트의 장점을 잘 활용한 좋은 접근법입니다.

</details>
<details>
<summary>src/app.module.ts (1)</summary>

`5-6`: **환경 설정 및 Prisma 모듈이 깔끔하게 통합되었습니다.**

루트 모듈에 `AppConfigModule`과 `PrismaModule`을 정상적으로 추가하셨습니다. 이를 통해 애플리케이션 전체에서 환경 설정 및 데이터베이스 접근 기능을 사용할 수 있게 되었습니다.

PR의 목표인 "환경 변수 관리 및 검증"과 "Prisma ORM 통합"이 잘 구현되었으며, 모듈 구조가 깔끔하게 정리되어 있습니다.



Also applies to: 9-9

</details>
<details>
<summary>src/prisma/prisma.service.ts (2)</summary>

`1-26`: **Prisma 서비스 구현이 잘 되었습니다.**

Prisma 서비스가 NestJS 라이프사이클 훅을 적절히 구현하여 애플리케이션 시작/종료 시 데이터베이스 연결을 안전하게 처리하고 있습니다. 환경설정 서비스를 주입받아 데이터베이스 URL과 로깅 레벨을 동적으로 구성하는 것도 좋은 접근법입니다.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/order)

---

[error] 1-1: Resolve error: typescript with invalid interface loaded as resolver

(import/no-duplicates)

</details>

</details>

---

`15-15`: **환경에 따른 로깅 설정이 잘 구현되었습니다.**

개발 환경에서는 상세한 로깅을 활성화하고 프로덕션 환경에서는 에러만 로깅하는 전략이 적절합니다. 다만, 향후 로깅 레벨을 더 세밀하게 제어할 필요가 있을 경우 환경 변수로 분리하는 것도 고려해 보세요.

</details>
<details>
<summary>src/config/configuration.ts (1)</summary>

`3-10`: **데이터베이스 설정이 잘 구성되었습니다.**

NestJS의 `registerAs` 함수를 사용하여 네임스페이스를 명확하게 분리한 점이 좋습니다. 기본 포트 설정도 적절합니다.

</details>
<details>
<summary>src/config/config.service.ts (3)</summary>

`4-29`: **인터페이스 정의가 잘 되어 있습니다.**

각 설정 영역별로 타입 안전성을 보장하는 인터페이스가 잘 정의되어 있습니다. 이는 애플리케이션 전반에 걸쳐 일관된 설정 접근을 가능하게 합니다.

---

`44-52`: **데이터베이스 설정 접근자가 타입 안전성을 잘 보장합니다.**

`getOrThrow` 메서드를 사용하여 필수 설정 값이 누락되었을 때 명확한 오류를 발생시키는 방식이 좋습니다. 포트와 같은 선택적 값에 대해 기본값을 제공하는 패턴도 적절합니다.

---

`83-93`: **환경 체크 헬퍼 메서드가 유용합니다.**

환경을 확인하는 헬퍼 메서드는 애플리케이션 전반에서 일관된 환경 확인을 가능하게 하는 좋은 접근법입니다. 이는 조건부 로직을 더 읽기 쉽게 만듭니다.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-26T07:42:28Z
user: login: jis-kim
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-26T07:49:56Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-26T07:50:19Z
user: login: jis-kim
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-26T07:52:05Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-26T07:52:33Z
user: login: jis-kim
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-26T07:52:50Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-26T07:53:18Z

Review comments:
diff_hunk: @@ -0,0 +1,39 @@
+import { PrismaClient } from '@prisma/client';
+
+const prisma = new PrismaClient();
+
+async function main(): Promise<void> {
+  // 학생 테스트 데이터 생성
+  const students = [
+    {
+      studentName: '김민준',
+    },
+    {
+      studentName: '이서연',
+    },
+    {
+      studentName: '박지우',
+    },
+    {
+      studentName: '최현우',
+    },
+    {
+      studentName: '정수아',
+    },
+  ];
+
+  for (const student of students) {
+    await prisma.student.create({
+      data: student,
+    });
+  }
+}
path: prisma/seed.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**중복 데이터 방지 로직이 없습니다.**

시드 스크립트가 여러 번 실행될 경우 동일한 학생 데이터가 중복으로 생성될 수 있습니다.


시드 실행 전에 기존 데이터를 확인하거나 트런케이트하는 로직을 추가하는 것이 좋습니다:

```diff
async function main(): Promise<void> {
+  // 기존 학생 데이터가 있는지 확인
+  const studentCount = await prisma.student.count();
+  
+  if (studentCount > 0) {
+    console.log('학생 데이터가 이미 존재합니다. 시드 작업을 건너뜁니다.');
+    return;
+  }
+
  // 학생 테스트 데이터 생성
  const students = [
    // ...existing student data
  ];

  // ...code to create students
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
async function main(): Promise<void> {
  // 기존 학생 데이터가 있는지 확인
  const studentCount = await prisma.student.count();

  if (studentCount > 0) {
    console.log('학생 데이터가 이미 존재합니다. 시드 작업을 건너뜁니다.');
    return;
  }

  // 학생 테스트 데이터 생성
  const students = [
    {
      studentName: '김민준',
    },
    {
      studentName: '이서연',
    },
    {
      studentName: '박지우',
    },
    {
      studentName: '최현우',
    },
    {
      studentName: '정수아',
    },
  ];

  for (const student of students) {
    await prisma.student.create({
      data: student,
    });
  }
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commits 5836629 to 3b66536

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-26T07:42:28Z
author_association: NONE
diff_hunk: @@ -0,0 +1,7 @@
+export interface BaseRepository<T> {
+  create(data: Partial<T>): Promise<T>;
+  findById(id: string): Promise<T | null>;
+  findAll(): Promise<T[]>;
+  update(id: string, data: Partial<T>): Promise<T>;
+  delete(id: string): Promise<T>;
+}
path: src/common/interfaces/base.repository.interface.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**기본 Repository 인터페이스에 몇 가지 기능이 부족합니다.**

기본적인 CRUD 작업을 위한 인터페이스가 잘 정의되어 있습니다. 하지만 실제 애플리케이션에서 필요할 수 있는 몇 가지 중요한 기능이 누락되어 있습니다.


아래와 같이 페이지네이션, 필터링 및 트랜잭션 관련 기능을 추가하는 것이 좋습니다:

```diff
export interface BaseRepository<T> {
  create(data: Partial<T>): Promise<T>;
+ createMany(data: Partial<T>[]): Promise<number>;
  findById(id: string): Promise<T | null>;
- findAll(): Promise<T[]>;
+ findAll(options?: {
+   skip?: number;
+   take?: number;
+   where?: Record<string, any>;
+   orderBy?: Record<string, 'asc' | 'desc'>;
+ }): Promise<{
+   data: T[];
+   total: number;
+ }>;
  update(id: string, data: Partial<T>): Promise<T>;
  delete(id: string): Promise<T>;
+ findOne(where: Record<string, any>): Promise<T | null>;
+ count(where?: Record<string, any>): Promise<number>;
+ transaction<R>(fn: (repo: BaseRepository<T>) => Promise<R>): Promise<R>;
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export interface BaseRepository<T> {
  create(data: Partial<T>): Promise<T>;
  createMany(data: Partial<T>[]): Promise<number>;
  findById(id: string): Promise<T | null>;
  findAll(options?: {
    skip?: number;
    take?: number;
    where?: Record<string, any>;
    orderBy?: Record<string, 'asc' | 'desc'>;
  }): Promise<{
    data: T[];
    total: number;
  }>;
  update(id: string, data: Partial<T>): Promise<T>;
  delete(id: string): Promise<T>;
  findOne(where: Record<string, any>): Promise<T | null>;
  count(where?: Record<string, any>): Promise<number>;
  transaction<R>(fn: (repo: BaseRepository<T>) => Promise<R>): Promise<R>;
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-26T07:42:28Z
author_association: NONE
diff_hunk: @@ -0,0 +1,39 @@
+import { registerAs } from '@nestjs/config';
+
+// 데이터베이스 설정
+export const databaseConfig = registerAs('database', () => ({
+  user: process.env.POSTGRES_USER,
+  password: process.env.POSTGRES_PASSWORD,
+  dbName: process.env.POSTGRES_DB,
+  port: parseInt(process.env.POSTGRES_PORT || '5432', 10),
+  url: process.env.DATABASE_URL,
+}));
+
+// JWT 설정
+export const jwtConfig = registerAs('jwt', () => ({
+  secret: process.env.JWT_SECRET,
+  expiresIn: process.env.JWT_EXPIRES_IN || '30d',
+}));
+
+// Azure OpenAI 설정
+export const azureOpenAIConfig = registerAs('azureOpenAI', () => ({
+  endpointUrl: process.env.AZURE_ENDPOINT_URL,
+  apiKey: process.env.AZURE_ENDPOINT_KEY,
+  apiVersion: process.env.OPEN_API_VERSION,
+  deploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME,
+}));
path: src/config/configuration.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**환경변수 이름에 일관성이 부족합니다.**

Azure OpenAI 설정에서 환경 변수 이름이 일관성이 부족합니다. `AZURE_ENDPOINT_URL`, `AZURE_ENDPOINT_KEY`와 `AZURE_OPENAI_DEPLOYMENT_NAME`은 비슷한 형식이지만, `OPEN_API_VERSION`은 `AZURE_` 접두사가 없습니다. 환경 변수 이름을 통일하여 관리하는 것이 좋습니다.


```diff
  endpointUrl: process.env.AZURE_ENDPOINT_URL,
  apiKey: process.env.AZURE_ENDPOINT_KEY,
- apiVersion: process.env.OPEN_API_VERSION,
+ apiVersion: process.env.AZURE_OPENAI_API_VERSION,
  deploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME,
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export const azureOpenAIConfig = registerAs('azureOpenAI', () => ({
  endpointUrl: process.env.AZURE_ENDPOINT_URL,
  apiKey: process.env.AZURE_ENDPOINT_KEY,
  apiVersion: process.env.AZURE_OPENAI_API_VERSION,
  deploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME,
}));
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-26T07:42:28Z
author_association: NONE
diff_hunk: @@ -0,0 +1,7 @@
+export interface BaseRepository<T> {
+  create(data: Partial<T>): Promise<T>;
+  findById(id: string): Promise<T | null>;
+  findAll(): Promise<T[]>;
+  update(id: string, data: Partial<T>): Promise<T>;
+  delete(id: string): Promise<T>;
+}
path: src/common/interfaces/base.repository.interface.ts
user: login: jis-kim
body: 이것은 필요한 때에 추가하도록 하겠습니다. 아주 좋은 아이디어 고맙습니다
created_at: 2025-04-26T07:49:56Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,7 @@
+export interface BaseRepository<T> {
+  create(data: Partial<T>): Promise<T>;
+  findById(id: string): Promise<T | null>;
+  findAll(): Promise<T[]>;
+  update(id: string, data: Partial<T>): Promise<T>;
+  delete(id: string): Promise<T>;
+}
path: src/common/interfaces/base.repository.interface.ts
user: login: coderabbitai[bot]
body: @jis-kim 네, 이해합니다. 필요할 때 기능을 추가하는 것이 좋은 접근 방식입니다. 현재는 기본적인 CRUD 작업에 초점을 맞추고, 추후에 요구사항이 생길 때 추가 기능을 구현하시면 됩니다. 피드백을 고려해 주셔서 감사합니다!


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@jis-kim 네, 이해합니다. 필요할 때 기능을 추가하는 것이 좋은 접근 방식입니다. 현재는 기본적인 CRUD 작업에 초점을 맞추고, 추후에 요구사항이 생길 때 추가 기능을 구현하시면 됩니다. 피드백을 고려해 주셔서 감사합니다!</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-26T07:50:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,39 @@
+import { registerAs } from '@nestjs/config';
+
+// 데이터베이스 설정
+export const databaseConfig = registerAs('database', () => ({
+  user: process.env.POSTGRES_USER,
+  password: process.env.POSTGRES_PASSWORD,
+  dbName: process.env.POSTGRES_DB,
+  port: parseInt(process.env.POSTGRES_PORT || '5432', 10),
+  url: process.env.DATABASE_URL,
+}));
+
+// JWT 설정
+export const jwtConfig = registerAs('jwt', () => ({
+  secret: process.env.JWT_SECRET,
+  expiresIn: process.env.JWT_EXPIRES_IN || '30d',
+}));
+
+// Azure OpenAI 설정
+export const azureOpenAIConfig = registerAs('azureOpenAI', () => ({
+  endpointUrl: process.env.AZURE_ENDPOINT_URL,
+  apiKey: process.env.AZURE_ENDPOINT_KEY,
+  apiVersion: process.env.OPEN_API_VERSION,
+  deploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME,
+}));
path: src/config/configuration.ts
user: login: jis-kim
body: azure API 명세에 해당하는 변수 명이 OPENAI_API_VERSION으로 AZURE 접두사 없이 표기되어있습니다.
혼동을 피하기 위해 AZURE_없이 OPENAI_API_VERSION으로 수정하겠습니다.
created_at: 2025-04-26T07:52:05Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,39 @@
+import { registerAs } from '@nestjs/config';
+
+// 데이터베이스 설정
+export const databaseConfig = registerAs('database', () => ({
+  user: process.env.POSTGRES_USER,
+  password: process.env.POSTGRES_PASSWORD,
+  dbName: process.env.POSTGRES_DB,
+  port: parseInt(process.env.POSTGRES_PORT || '5432', 10),
+  url: process.env.DATABASE_URL,
+}));
+
+// JWT 설정
+export const jwtConfig = registerAs('jwt', () => ({
+  secret: process.env.JWT_SECRET,
+  expiresIn: process.env.JWT_EXPIRES_IN || '30d',
+}));
+
+// Azure OpenAI 설정
+export const azureOpenAIConfig = registerAs('azureOpenAI', () => ({
+  endpointUrl: process.env.AZURE_ENDPOINT_URL,
+  apiKey: process.env.AZURE_ENDPOINT_KEY,
+  apiVersion: process.env.OPEN_API_VERSION,
+  deploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME,
+}));
path: src/config/configuration.ts
user: login: coderabbitai[bot]
body: @jis-kim 네, 말씀해주신 Azure API 명세를 따르는 것이 더 좋습니다. `OPENAI_API_VERSION`로 수정하시는 것이 공식 문서와 일치하므로 적절한 결정입니다. 조언 감사합니다!

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: jis-kim
PR: jis-kim/essay-evaluation#15
File: src/config/configuration.ts:19-24
Timestamp: 2025-04-26T07:52:32.480Z
Learning: Azure OpenAI API의 환경 변수 중 API 버전은 Azure 문서에 따라 `OPENAI_API_VERSION`으로 명명하는 것이 표준이며, 다른 Azure 관련 변수들과 달리 `AZURE_` 접두사를 사용하지 않는다.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@jis-kim 네, 말씀해주신 Azure API 명세를 따르는 것이 더 좋습니다. `OPENAI_API_VERSION`로 수정하시는 것이 공식 문서와 일치하므로 적절한 결정입니다. 조언 감사합니다!

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: jis-kim
PR: jis-kim/essay-evaluation#15
File: src/config/configuration.ts:19-24
Timestamp: 2025-04-26T07:52:32.480Z
Learning: Azure OpenAI API의 환경 변수 중 API 버전은 Azure 문서에 따라 `OPENAI_API_VERSION`으로 명명하는 것이 표준이며, 다른 Azure 관련 변수들과 달리 `AZURE_` 접두사를 사용하지 않는다.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-26T07:52:33Z
author_association: NONE
diff_hunk: @@ -0,0 +1,39 @@
+import { PrismaClient } from '@prisma/client';
+
+const prisma = new PrismaClient();
+
+async function main(): Promise<void> {
+  // 학생 테스트 데이터 생성
+  const students = [
+    {
+      studentName: '김민준',
+    },
+    {
+      studentName: '이서연',
+    },
+    {
+      studentName: '박지우',
+    },
+    {
+      studentName: '최현우',
+    },
+    {
+      studentName: '정수아',
+    },
+  ];
+
+  for (const student of students) {
+    await prisma.student.create({
+      data: student,
+    });
+  }
+}
path: prisma/seed.ts
user: login: jis-kim
body: count해서 기존 데이터를 확인하는 방향으로 개선하겠습니다.
created_at: 2025-04-26T07:52:50Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,39 @@
+import { PrismaClient } from '@prisma/client';
+
+const prisma = new PrismaClient();
+
+async function main(): Promise<void> {
+  // 학생 테스트 데이터 생성
+  const students = [
+    {
+      studentName: '김민준',
+    },
+    {
+      studentName: '이서연',
+    },
+    {
+      studentName: '박지우',
+    },
+    {
+      studentName: '최현우',
+    },
+    {
+      studentName: '정수아',
+    },
+  ];
+
+  for (const student of students) {
+    await prisma.student.create({
+      data: student,
+    });
+  }
+}
path: prisma/seed.ts
user: login: coderabbitai[bot]
body: @jis-kim 네, 좋은 결정입니다! 기존 데이터를 확인하는 로직을 추가하면 중복 데이터 생성 문제를 효과적으로 방지할 수 있을 것입니다. 구현에 도움이 필요하시면 말씀해 주세요.

⌒(・ω・)⌒


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@jis-kim 네, 좋은 결정입니다! 기존 데이터를 확인하는 로직을 추가하면 중복 데이터 생성 문제를 효과적으로 방지할 수 있을 것입니다. 구현에 도움이 필요하시면 말씀해 주세요.

⌒(・ω・)⌒</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-26T07:53:18Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-26T07:40:00Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The pull request is closed.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

"""
## Walkthrough

이번 변경 사항에서는 NestJS 기반 애플리케이션의 환경설정, Prisma ORM 연동, 데이터베이스 시드 스크립트, 공통 Repository 인터페이스 도입 등이 이루어졌습니다. 환경 변수 예시 파일과 환경 변수 유효성 검증, 설정 모듈 및 서비스, Prisma 모듈 및 서비스가 추가되었으며, 데이터베이스 초기화용 시드 스크립트가 포함되었습니다. 또한, 공통 CRUD Repository 인터페이스가 도입되어 기본적인 데이터 접근 패턴을 정의합니다. 패키지 의존성도 추가되었습니다.

## Changes

| 파일/경로                                            | 변경 요약                                                                                                 |
|------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| .env.example                                        | 환경 변수 예시 파일 추가. 애플리케이션, 데이터베이스, Azure, JWT 설정을 위한 변수 템플릿 제공.            |
| package.json                                        | `@nestjs/config` 및 `zod` 패키지 의존성 추가.                                                            |
| prisma/seed.ts                                      | Prisma를 이용한 학생 데이터 시드 스크립트 추가.                                                           |
| src/app.module.ts                                   | `AppConfigModule`, `PrismaModule`를 메인 모듈에 imports로 추가.                                          |
| src/common/interfaces/base.repository.interface.ts   | 제네릭 CRUD 인터페이스(BaseRepository) 추가.                                                             |
| src/config/config.module.ts                         | 전역 AppConfigModule 추가 및 환경 변수 검증, 설정 모듈 로딩 구현.                                         |
| src/config/config.service.ts                        | AppConfigService 추가: 환경설정 값 타입 안전하게 제공, 각 도메인별 getter 구현.                           |
| src/config/configuration.ts                         | 데이터베이스, JWT, Azure, 앱 설정 등 환경설정 객체들 등록 및 export.                                      |
| src/config/env.validation.ts                        | zod 기반 환경 변수 유효성 검증 스키마 및 타입 추가.                                                      |
| src/prisma/prisma.module.ts                         | 전역 PrismaModule 추가, PrismaService 제공 및 export.                                                    |
| src/prisma/prisma.service.ts                        | PrismaService 구현: PrismaClient 상속, 환경설정 기반 DB 연결 및 라이프사이클 관리.                        |
| eslint.config.mjs                                   | `no-console` 규칙 제거로 콘솔 사용 제한 완화.                                                             |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant AppModule
    participant AppConfigModule
    participant PrismaModule
    participant PrismaService
    participant Database

    AppModule->>AppConfigModule: Import 및 초기화
    AppModule->>PrismaModule: Import 및 초기화
    PrismaModule->>PrismaService: PrismaService 제공
    PrismaService->>AppConfigModule: DB 설정값 요청
    PrismaService->>Database: DB 연결 (onModuleInit)
```

## Assessment against linked issues

| Objective                                                                 | Addressed | Explanation                                                      |
|---------------------------------------------------------------------------|-----------|------------------------------------------------------------------|
| Prisma 설정 및 데이터베이스 스키마 정의 (#2)                              | ✅        |                                                                  |

## Possibly related PRs

- jis-kim/essay-evaluation#15: `.env.example` 파일 추가 및 Prisma 및 설정 모듈 관련 초기 구성 변경으로 본 PR과 기능 및 구조적으로 밀접한 관련이 있음.

## Poem

> 토끼가 깡총, 코드를 퐁퐁  
> 환경설정도, Prisma도 이제 완벽해!  
> 시드 데이터로 학생도 추가,  
> 설정 서비스로 타입도 안전하구나.  
> Repository도 준비 완료,  
> 개발자들아, 이제 걱정 말고 달려보자!  
> 🐇✨
"""

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b805f2065a93e950496572940c36e0a1db173d37 and 3b66536286cd13dae76f2f928d01f8ca10938ce0.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `.env.example` (1 hunks)
* `eslint.config.mjs` (1 hunks)
* `prisma/seed.ts` (1 hunks)
* `src/config/config.module.ts` (1 hunks)
* `src/config/configuration.ts` (1 hunks)
* `src/config/env.validation.ts` (1 hunks)

</details>

</details>
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=jis-kim/essay-evaluation&utm_content=15):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-26T07:42:14Z
body: > [!NOTE]
> Generated docstrings for this pull request at https://github.com/jis-kim/essay-evaluation/pull/16
author_association: NONE