From 39e74839741df4c9af93ddac8379bbc4cf58fa3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=B0=AC=ED=98=81?= Date: Wed, 29 Apr 2026 22:11:08 +0900 Subject: [PATCH 1/7] Update issue templates --- ...354\266\234\355\225\251\353\213\210\353\213\244.md" | 10 ++++++++++ ...55\201\254\353\266\201-\354\235\264\354\212\210.md" | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 ".github/ISSUE_TEMPLATE/-00\354\260\250-\354\260\254\354\260\254-\354\233\214\355\201\254\353\266\201-\354\240\234\354\266\234\355\225\251\353\213\210\353\213\244.md" diff --git "a/.github/ISSUE_TEMPLATE/-00\354\260\250-\354\260\254\354\260\254-\354\233\214\355\201\254\353\266\201-\354\240\234\354\266\234\355\225\251\353\213\210\353\213\244.md" "b/.github/ISSUE_TEMPLATE/-00\354\260\250-\354\260\254\354\260\254-\354\233\214\355\201\254\353\266\201-\354\240\234\354\266\234\355\225\251\353\213\210\353\213\244.md" new file mode 100644 index 0000000..9c9bb7c --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/-00\354\260\250-\354\260\254\354\260\254-\354\233\214\355\201\254\353\266\201-\354\240\234\354\266\234\355\225\251\353\213\210\353\213\244.md" @@ -0,0 +1,10 @@ +--- +name: "[00차/찬찬]워크북 제출합니다" +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git "a/.github/ISSUE_TEMPLATE/\354\233\214\355\201\254\353\266\201-\354\235\264\354\212\210.md" "b/.github/ISSUE_TEMPLATE/\354\233\214\355\201\254\353\266\201-\354\235\264\354\212\210.md" index 991b3c2..1d2c061 100644 --- "a/.github/ISSUE_TEMPLATE/\354\233\214\355\201\254\353\266\201-\354\235\264\354\212\210.md" +++ "b/.github/ISSUE_TEMPLATE/\354\233\214\355\201\254\353\266\201-\354\235\264\354\212\210.md" @@ -2,8 +2,9 @@ name: 워크북 이슈 템플릿 about: 해당 이슈 생성 템플릿을 사용하여 워크북 이슈를 생성합니다. title: '"Chapter00 워크북 제목"' -labels: "" -assignees: seoki +labels: '' +assignees: '' + --- ### 📚 워크북 From e7c9a6e97aa2c3b328cea775e2a57248bddc148d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=B0=AC=ED=98=81?= Date: Wed, 13 May 2026 18:16:30 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=207=EC=A3=BC=EC=B0=A8=20Tsoa=20?= =?UTF-8?q?=EB=B0=8F=20=EB=AF=B8=EB=93=A4=EC=9B=A8=EC=96=B4=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- misson_practice/chapter07/README.md | 1343 ++++++ .../images/practice-01-tsoa-install.png | Bin 0 -> 5162 bytes .../chapter07/images/practice-02-tsconfig.png | Bin 0 -> 8424 bytes .../images/practice-03-tsoa-json.png | Bin 0 -> 15351 bytes .../images/practice-06-signup-success.png | Bin 0 -> 7262 bytes .../images/practice-07-signup-fail.png | Bin 0 -> 22681 bytes .../images/practice-09-guest-page.png | Bin 0 -> 8243 bytes .../images/practice-10-login-required.png | Bin 0 -> 9115 bytes .../images/practice-11-set-login.png | Bin 0 -> 9972 bytes .../images/practice-12-mypage-success.png | Bin 0 -> 10070 bytes .../chapter07/images/practice-13-logout.png | Bin 0 -> 7305 bytes package-lock.json | 3625 +++++++++++++++-- package.json | 34 +- prisma.config.ts | 9 + .../20260506151555_init/migration.sql | 126 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 126 + src/common/errors/app.error.ts | 18 + src/common/errors/user.error.ts | 12 + src/common/middlewares/auth.middleware.ts | 21 + src/common/responses/response.ts | 11 + src/db.config.ts | 17 + src/generated/routes.ts | 252 ++ src/index.ts | 65 + .../controllers/mission.controller.ts | 153 + src/modules/missions/dtos/mission.dto.ts | 108 + .../repositories/mission.repository.ts | 175 + .../missions/services/mission.service.ts | 132 + .../reviews/controllers/review.controller.ts | 62 + src/modules/reviews/dtos/review.dto.ts | 48 + .../reviews/repositories/review.repository.ts | 79 + .../reviews/services/review.service.ts | 42 + .../stores/controllers/store.controller.ts | 30 + src/modules/stores/dtos/store.dto.ts | 21 + .../stores/repositories/store.repository.ts | 33 + src/modules/stores/services/store.service.ts | 24 + .../users/controllers/user.controller.ts | 94 + src/modules/users/dtos/user.dto.ts | 20 + .../users/repositories/user.repository.ts | 73 + src/modules/users/services/user.service.ts | 51 + tsconfig.json | 2 +- tsoa.json | 12 + 43 files changed, 6485 insertions(+), 341 deletions(-) create mode 100644 misson_practice/chapter07/README.md create mode 100644 misson_practice/chapter07/images/practice-01-tsoa-install.png create mode 100644 misson_practice/chapter07/images/practice-02-tsconfig.png create mode 100644 misson_practice/chapter07/images/practice-03-tsoa-json.png create mode 100644 misson_practice/chapter07/images/practice-06-signup-success.png create mode 100644 misson_practice/chapter07/images/practice-07-signup-fail.png create mode 100644 misson_practice/chapter07/images/practice-09-guest-page.png create mode 100644 misson_practice/chapter07/images/practice-10-login-required.png create mode 100644 misson_practice/chapter07/images/practice-11-set-login.png create mode 100644 misson_practice/chapter07/images/practice-12-mypage-success.png create mode 100644 misson_practice/chapter07/images/practice-13-logout.png create mode 100644 prisma.config.ts create mode 100644 prisma/migrations/20260506151555_init/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 src/common/errors/app.error.ts create mode 100644 src/common/errors/user.error.ts create mode 100644 src/common/middlewares/auth.middleware.ts create mode 100644 src/common/responses/response.ts create mode 100644 src/db.config.ts create mode 100644 src/generated/routes.ts create mode 100644 src/index.ts create mode 100644 src/modules/missions/controllers/mission.controller.ts create mode 100644 src/modules/missions/dtos/mission.dto.ts create mode 100644 src/modules/missions/repositories/mission.repository.ts create mode 100644 src/modules/missions/services/mission.service.ts create mode 100644 src/modules/reviews/controllers/review.controller.ts create mode 100644 src/modules/reviews/dtos/review.dto.ts create mode 100644 src/modules/reviews/repositories/review.repository.ts create mode 100644 src/modules/reviews/services/review.service.ts create mode 100644 src/modules/stores/controllers/store.controller.ts create mode 100644 src/modules/stores/dtos/store.dto.ts create mode 100644 src/modules/stores/repositories/store.repository.ts create mode 100644 src/modules/stores/services/store.service.ts create mode 100644 src/modules/users/controllers/user.controller.ts create mode 100644 src/modules/users/dtos/user.dto.ts create mode 100644 src/modules/users/repositories/user.repository.ts create mode 100644 src/modules/users/services/user.service.ts create mode 100644 tsoa.json diff --git a/.gitignore b/.gitignore index 4d64059..10a286d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ /node_modules - +.env +.env.* +dist/ +src/generated/prisma/ \ No newline at end of file diff --git a/misson_practice/chapter07/README.md b/misson_practice/chapter07/README.md new file mode 100644 index 0000000..13af5b3 --- /dev/null +++ b/misson_practice/chapter07/README.md @@ -0,0 +1,1343 @@ + +# 7주차 실습 README + +이번 7주차 실습에서는 기존 Express 프로젝트에 Tsoa를 적용하고, Express 미들웨어와 API 응답 통일, 에러 핸들링 구조를 적용했다. + +6주차까지는 Prisma ORM을 이용해 API 기능 구현에 집중했다면, 7주차에서는 API 구조를 더 일관성 있게 만들기 위해 다음 내용을 순차적으로 진행했다. + +- Tsoa 적용 +- Express 미들웨어 적용 +- morgan, cookie-parser 적용 +- 성공 응답 형식 통일 +- 실패 응답 형식 통일 +- 커스텀 Error 객체 적용 +- 전역 에러 핸들러 적용 +- Postman으로 성공/실패 응답 테스트 + +--- + +## 0. GitHub 작업 정보 + +### Issue + +```text +[Practice] 7주차 Tsoa, 미들웨어, 응답 통일 실습 +```` + +### Branch + +```text +feature/chapter-07 +``` + +### 작업 방식 + +이번 7주차 실습은 실습 코드와 README 정리가 함께 포함되므로 하나의 브랜치에서 진행했다. + +커밋은 기능별로 분리했다. + +```text +feat: 7주차 Tsoa 및 미들웨어 적용 +docs: 7주차 실습 README 정리 +docs: 7주차 미션 README 정리 +``` + +### 브랜치 이동 + +```bash +cd C:\Project\10th_Node.js_Practice_Mission + +git fetch origin +git switch feature/chapter-07 +``` + +만약 브랜치가 로컬에 없다면 다음 명령어를 사용한다. + +```bash +git switch -c feature/chapter-07 origin/feature/chapter-07 +``` + +현재 브랜치 확인: + +```bash +git branch +``` + +정상이라면 아래처럼 표시된다. + +```text +* feature/chapter-07 +``` + +--- + +## 1. 실습 전 현재 상태 확인 + +먼저 현재 프로젝트 상태를 확인했다. + +```bash +git status +``` + +올라가면 안 되는 파일이 있는지 확인한다. + +```text +.env +node_modules/ +dist/ +``` + +위 파일들은 GitHub에 올라가면 안 되므로 `.gitignore`에 포함되어 있어야 한다. + +`.gitignore` 확인: + +```gitignore +node_modules/ +.env +.env.* +dist/ +src/generated/prisma/ +``` + +--- + +## 2. Tsoa 설치 + +### 2-1. 설치 명령어 + +```bash +npm install express tsoa +``` + +설치 중 다음과 같은 경고가 나올 수 있다. + +```text +npm warn deprecated glob@10.5.0 +3 vulnerabilities +``` + +### 2-2. 로그 해석 + +이 경고는 설치 실패가 아니라, 하위 의존성 중 일부 패키지에서 deprecated 또는 보안 취약점 경고가 발생했다는 의미이다. + +설치 자체는 아래처럼 완료되면 성공이다. + +```text +added ... packages +audited ... packages +``` + +### 2-3. 설치 확인 + +```bash +npm list tsoa +npm list express +``` + +또는 Tsoa 버전 확인: + +```bash +npx tsoa --version +``` + +### 실습 인증 사진 + + +![Tsoa 설치 확인](./images/practice-01-tsoa-install.png) + +--- + +## 3. tsconfig.json 수정 + +Tsoa는 데코레이터 문법을 사용하므로 `tsconfig.json`에 아래 옵션을 추가한다. + +```json +{ + "compilerOptions": { + "experimentalDecorators": true + } +} +``` + +이미 `compilerOptions`가 있다면 그 안에 `"experimentalDecorators": true`만 추가하면 된다. + +### 예시 + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "outDir": "./dist" + } +} +``` + +### 왜 필요한가? + +`@Route`, `@Post`, `@Get`, `@Tags` 같은 Tsoa 데코레이터를 사용하기 위해 필요하다. + +### 실습 인증 사진 + + +![tsconfig decorator 설정](./images/practice-02-tsconfig.png) + +--- + +## 4. tsoa.json 생성 + +프로젝트 루트에 `tsoa.json` 파일을 생성한다. + +```text +10th_Node.js_Practice_Mission +└── tsoa.json +``` + +내용은 다음과 같이 작성한다. + +```json +{ + "entryFile": "src/index.ts", + "noImplicitAdditionalProperties": "throw-on-extras", + "controllerPathGlobs": ["src/**/*.controller.ts"], + "spec": { + "outputDirectory": "dist", + "specVersion": 3 + }, + "routes": { + "routesDir": "src/generated" + } +} +``` + +### 설정 설명 + +| 설정 | 의미 | +| -------------------------------- | --------------------------- | +| `entryFile` | Express 서버의 진입 파일 | +| `controllerPathGlobs` | Tsoa가 controller 파일을 찾는 경로 | +| `noImplicitAdditionalProperties` | DTO에 없는 추가 필드가 들어왔을 때 에러 처리 | +| `spec.outputDirectory` | OpenAPI 문서 생성 위치 | +| `routes.routesDir` | 자동 생성되는 routes.ts 위치 | + +### 실습 인증 사진 + + +![tsoa.json 생성](./images/practice-03-tsoa-json.png) + +--- + +## 5. 공통 응답 폴더 생성 + +성공 응답 구조를 통일하기 위해 공통 응답 파일을 만든다. + +### 폴더 생성 + +```bash +mkdir -p src/common/responses +``` + +PowerShell에서는 다음처럼 해도 된다. + +```powershell +mkdir src\common\responses +``` + +### 파일 생성 + +```text +src/common/responses/response.ts +``` + +### response.ts 작성 + +```ts +export interface ApiResponse { + resultType: "SUCCESS"; + error: null; + success: T; +} + +export const success = (data: T): ApiResponse => ({ + resultType: "SUCCESS", + error: null, + success: data, +}); +``` + +### 코드 설명 + +```ts +resultType: "SUCCESS" +``` + +요청이 성공했음을 나타낸다. + +```ts +error: null +``` + +성공 응답이므로 에러는 없다. + +```ts +success: data +``` + +실제 응답 데이터는 `success` 안에 담는다. + +--- + +## 6. 공통 Error 폴더 생성 + +실패 응답을 통일하기 위해 커스텀 Error 클래스를 만든다. + +### 폴더 생성 + +```bash +mkdir -p src/common/errors +``` + +PowerShell: + +```powershell +mkdir src\common\errors +``` + +--- + +## 7. AppError 생성 + +### 파일 생성 + +```text +src/common/errors/app.error.ts +``` + +### 코드 작성 + +```ts +export class AppError extends Error { + public readonly errorCode: string; + public readonly statusCode: number; + public readonly data?: unknown; + + constructor(params: { + errorCode: string; + message: string; + statusCode: number; + data?: unknown; + }) { + super(params.message); + + this.errorCode = params.errorCode; + this.statusCode = params.statusCode; + this.data = params.data ?? null; + } +} +``` + +### 코드 설명 + +기본 `Error` 객체는 주로 `message`만 가진다. +하지만 API 응답에서는 다음 정보가 필요하다. + +```text +errorCode +statusCode +message +data +``` + +그래서 `AppError`를 만들어 공통 에러 구조를 정의했다. + +--- + +## 8. 사용자 관련 커스텀 Error 생성 + +### 파일 생성 + +```text +src/common/errors/user.error.ts +``` + +### 코드 작성 + +```ts +import { AppError } from "./app.error.js"; + +export class DuplicateUserEmailError extends AppError { + constructor(data?: unknown) { + super({ + errorCode: "U001", + message: "이미 존재하는 이메일입니다.", + statusCode: 409, + data, + }); + } +} +``` + +### 코드 설명 + +```ts +errorCode: "U001" +``` + +이메일 중복 오류를 구분하기 위한 코드이다. + +```ts +statusCode: 409 +``` + +이미 존재하는 이메일은 기존 리소스 상태와 충돌하는 상황이므로 `409 Conflict`로 처리했다. + +--- + +## 9. User DTO 정리 + +Tsoa에서는 Controller에서 요청/응답 타입을 직접 사용하므로 DTO를 인터페이스 중심으로 정리한다. + +### 파일 위치 + +```text +src/modules/users/dtos/user.dto.ts +``` + +### 코드 예시 + +```ts +export interface UserSignUpRequest { + email: string; + password?: string; + name: string; + gender: string; + birth: Date | string; + address?: string; + detailAddress?: string; + phoneNumber: string; + preferences: number[]; +} + +export interface UserSignUpResponse { + userId: number; + preferences: string[]; +} +``` + +### 정리 + +기존에 `bodyToUser()` 같은 변환 함수가 있었다면, Tsoa 방식에서는 Controller에서 `@Body()`로 직접 타입을 받기 때문에 DTO는 요청과 응답의 타입 정의 중심으로 정리한다. + +--- + +## 10. User Service 수정 + +이메일 중복 오류가 발생할 때 일반 Error가 아니라 커스텀 Error를 던지도록 수정한다. + +### 파일 위치 + +```text +src/modules/users/services/user.service.ts +``` + +### 코드 예시 + +```ts +import { UserSignUpRequest, UserSignUpResponse } from "../dtos/user.dto.js"; +import { + addUser, + getUser, + getUserPreferencesByUserId, + setPreference, +} from "../repositories/user.repository.js"; +import { DuplicateUserEmailError } from "../../../common/errors/user.error.js"; + +export const userSignUp = async ( + data: UserSignUpRequest +): Promise => { + const joinUserId = await addUser({ + email: data.email, + password: data.password, + name: data.name, + gender: data.gender, + birth: new Date(data.birth), + address: data.address ?? "", + detailAddress: data.detailAddress ?? "", + phoneNumber: data.phoneNumber, + }); + + if (joinUserId === null) { + throw new DuplicateUserEmailError(data); + } + + for (const preference of data.preferences) { + await setPreference(joinUserId, preference); + } + + const user = await getUser(joinUserId); + + const preferences = (await getUserPreferencesByUserId(joinUserId)).map( + (obj) => obj.foodCategory.name + ); + + return { + userId: user.id, + preferences, + }; +}; +``` + +### 변경 전 + +```ts +throw new Error("이미 존재하는 이메일입니다."); +``` + +### 변경 후 + +```ts +throw new DuplicateUserEmailError(data); +``` + +--- + +## 11. User Controller를 Tsoa 방식으로 변경 + +기존 Express 함수형 컨트롤러를 Tsoa 클래스 기반 컨트롤러로 변경한다. + +### 파일 위치 + +```text +src/modules/users/controllers/user.controller.ts +``` + +### 기존 방식 + +```ts +export const handleUserSignUp = async (req, res, next) => { + const user = await userSignUp(req.body); + + res.status(200).json({ + result: user, + }); +}; +``` + +### Tsoa 방식 + +```ts +import { Body, Controller, Post, Route, Tags } from "tsoa"; +import { UserSignUpRequest, UserSignUpResponse } from "../dtos/user.dto.js"; +import { userSignUp } from "../services/user.service.js"; +import { ApiResponse, success } from "../../../common/responses/response.js"; + +@Route("users") +@Tags("Users") +export class UserController extends Controller { + @Post("signup") + public async handleUserSignUp( + @Body() body: UserSignUpRequest + ): Promise> { + console.log("회원가입을 요청했습니다!"); + console.log("body:", body); + + const user = await userSignUp(body); + + return success(user); + } +} +``` + +### 코드 설명 + +```ts +@Route("users") +``` + +이 컨트롤러의 기본 경로를 의미한다. +최종적으로 `/api/v1/users`로 연결된다. + +```ts +@Post("signup") +``` + +`POST /users/signup` 엔드포인트를 의미한다. + +```ts +@Body() body: UserSignUpRequest +``` + +요청 body를 `UserSignUpRequest` 타입으로 받는다. + +```ts +return success(user); +``` + +성공 응답을 공통 응답 형식으로 감싸서 반환한다. + +--- + +## 12. Tsoa routes 생성 + +컨트롤러를 Tsoa 방식으로 수정한 뒤 routes 파일을 생성한다. + +```bash +npx tsoa spec-and-routes +``` + +정상적으로 생성되면 아래 파일이 생긴다. + +```text +src/generated/routes.ts +``` + +### 확인 + +```bash +ls src/generated +``` + +PowerShell: + +```powershell +dir src\generated +``` + +### 실습 인증 사진 + +```md +![Tsoa routes 생성](./images/practice-04-generated-routes.png) +``` + +--- + +## 13. index.ts에 Tsoa routes 연결 + +### 파일 위치 + +```text +src/index.ts +``` + +기존에는 `index.ts`에서 직접 라우트를 등록했다. + +```ts +app.post("/api/v1/users/signup", handleUserSignUp); +``` + +이제 Tsoa가 생성한 routes를 연결한다. + +### index.ts 예시 + +```ts +import dotenv from "dotenv"; +import express, { Express, NextFunction, Request, Response } from "express"; +import cors from "cors"; +import { RegisterRoutes } from "./generated/routes.js"; +import { AppError } from "./common/errors/app.error.js"; + +dotenv.config(); + +const app: Express = express(); +const port = process.env.PORT || 3000; + +app.use(cors()); +app.use(express.static("public")); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); + +const router = express.Router(); + +RegisterRoutes(router); + +app.use("/api/v1", router); + +app.use((err: AppError, req: Request, res: Response, next: NextFunction) => { + if (res.headersSent) { + return next(err); + } + + res.status(err.statusCode || 500).json({ + resultType: "FAIL", + error: { + errorCode: err.errorCode || "UNKNOWN", + reason: err.message || "서버 오류가 발생했습니다.", + data: err.data || null, + }, + success: null, + }); +}); + +app.listen(port, () => { + console.log(`[server]: Server is running at http://localhost:${port}`); +}); +``` + +### 중요한 순서 + +```ts +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); + +RegisterRoutes(router); +app.use("/api/v1", router); + +app.use((err, req, res, next) => { + ... +}); +``` + +전역 에러 핸들러는 routes 등록 이후에 위치해야 한다. + +--- + +## 14. package.json scripts 수정 + +서버 실행 전 Tsoa routes를 자동 생성하도록 scripts를 수정한다. + +### package.json + +```json +{ + "scripts": { + "start": "tsoa spec-and-routes && tsx src/index.ts", + "dev": "tsoa spec-and-routes && nodemon --exec tsx src/index.ts" + } +} +``` + +### 의미 + +```text +tsoa spec-and-routes +→ src/generated/routes.ts 생성 +→ tsx src/index.ts 실행 +``` + +이렇게 하면 컨트롤러 수정 후 routes 생성을 깜빡하는 문제를 줄일 수 있다. + +--- + +## 15. 서버 실행 테스트 + +```bash +npm run dev +``` + +정상 실행 예시: + +```text +[server]: Server is running at http://localhost:3000 +``` + +### 실습 인증 사진 + +```md +![서버 실행 성공](./images/practice-05-server-run.png) +``` + +--- + +## 16. Postman으로 회원가입 성공 응답 테스트 + +### Request + +```text +POST http://localhost:3000/api/v1/users/signup +``` + +### Body + +```json +{ + "email": "new-user@example.com", + "password": "1234abcd!", + "name": "새유저", + "gender": "남성", + "birth": "2000-02-03", + "address": "주소1", + "detailAddress": "세부주소1", + "phoneNumber": "010-0000-0000", + "preferences": [1, 2] +} +``` + +### 예상 Response + +```json +{ + "resultType": "SUCCESS", + "error": null, + "success": { + "userId": 2, + "preferences": ["한식", "중식"] + } +} +``` + +### 확인할 점 + +```text +resultType이 SUCCESS인지 확인 +error가 null인지 확인 +실제 데이터가 success 안에 들어가는지 확인 +``` + +### 실습 인증 사진 + + +![회원가입 성공 응답](./images/practice-06-signup-success.png) + + +--- + +## 17. Postman으로 중복 이메일 실패 응답 테스트 + +같은 이메일로 다시 요청을 보낸다. + +### Request + +```text +POST http://localhost:3000/api/v1/users/signup +``` + +### Body + +```json +{ + "email": "new-user@example.com", + "password": "1234abcd!", + "name": "새유저", + "gender": "남성", + "birth": "2000-02-03", + "address": "주소1", + "detailAddress": "세부주소1", + "phoneNumber": "010-0000-0000", + "preferences": [1, 2] +} +``` + +### 예상 Response + +```json +{ + "resultType": "FAIL", + "error": { + "errorCode": "U001", + "reason": "이미 존재하는 이메일입니다.", + "data": { + "email": "new-user@example.com", + "password": "1234abcd!", + "name": "새유저", + "gender": "남성", + "birth": "2000-02-03", + "address": "주소1", + "detailAddress": "세부주소1", + "phoneNumber": "010-0000-0000", + "preferences": [1, 2] + } + }, + "success": null +} +``` + +### 확인할 점 + +```text +resultType이 FAIL인지 확인 +errorCode가 U001인지 확인 +reason이 이미 존재하는 이메일입니다.인지 확인 +success가 null인지 확인 +HTTP Status가 409인지 확인 +``` + +### 실습 인증 사진 + + +![중복 이메일 실패 응답](./images/practice-07-signup-fail.png) + + +--- + +## 18. morgan 설치 및 적용 + +### 설치 + +```bash +npm install morgan +npm install -D @types/morgan +``` + +### index.ts 적용 + +```ts +import morgan from "morgan"; + +app.use(morgan("dev")); +``` + +`cors()` 아래, 라우터 등록 전에 작성한다. + +```ts +app.use(cors()); +app.use(morgan("dev")); +app.use(express.static("public")); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +``` + +### 확인 + +Postman으로 아무 API나 요청하면 터미널에 로그가 출력된다. + +```text +POST /api/v1/users/signup 409 12.456 ms - 350 +``` + +--- + +## 19. cookie-parser 설치 및 적용 + +### 설치 + +```bash +npm install cookie-parser +npm install -D @types/cookie-parser +``` + +### index.ts 적용 + +```ts +import cookieParser from "cookie-parser"; + +app.use(cookieParser()); +``` + +적용 위치: + +```ts +app.use(cors()); +app.use(morgan("dev")); +app.use(cookieParser()); +app.use(express.static("public")); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +``` + +--- + +## 20. 인증 미들웨어 생성 + +### 폴더 생성 + +```bash +mkdir -p src/common/middlewares +``` + +PowerShell: + +```powershell +mkdir src\common\middlewares +``` + +### 파일 생성 + +```text +src/common/middlewares/auth.middleware.ts +``` + +### 코드 작성 + +```ts +import { Request, Response, NextFunction } from "express"; + +export function authorizeUser() { + return async (req: Request, res: Response, next: NextFunction) => { + const { username } = req.cookies; + + if (username) { + console.log(`[인증 성공] ${username}님, 환영합니다.`); + next(); + } else { + console.log("[인증 실패] 로그인이 필요합니다."); + + res + .status(401) + .send( + '' + ); + } + }; +} +``` + +### 코드 설명 + +```ts +const { username } = req.cookies; +``` + +`cookie-parser`를 통해 쿠키 값을 객체로 읽는다. + +```ts +next(); +``` + +로그인 쿠키가 있으면 다음 단계로 넘어간다. + +```ts +res.status(401) +``` + +쿠키가 없으면 인증 실패로 처리한다. + +--- + +## 21. Tsoa Controller에 인증 예제 추가 + +`src/modules/users/controllers/user.controller.ts`에 아래 import를 추가한다. + +```ts +import { + Body, + Controller, + Get, + Middlewares, + Post, + Request, + Route, + Tags, +} from "tsoa"; +import { Request as ExpressRequest } from "express"; +import { authorizeUser } from "../../../common/middlewares/auth.middleware.js"; +``` + +UserController 안에 아래 메서드들을 추가한다. + +```ts +@Get("guest") +public async handleGuestPage(): Promise { + return ` +

게스트 페이지

+

이 페이지는 로그인이 필요 없습니다.

+ + `; +} + +@Get("login") +public async handleLoginPage(): Promise { + return "

로그인 페이지

로그인이 필요한 페이지에서 이동합니다.

"; +} + +@Get("mypage") +@Middlewares(authorizeUser()) +public async handleMypage( + @Request() req: ExpressRequest +): Promise { + return ` +

마이페이지

+

환영합니다, ${req.cookies.username}님!

+

이 페이지는 로그인한 사람만 볼 수 있습니다.

+ `; +} + +@Get("set-login") +public async handleSetLogin( + @Request() req: ExpressRequest +): Promise { + req.res!.cookie("username", "UMC10th", { maxAge: 3600000 }); + + return '로그인 쿠키 생성 완료! 마이페이지로 이동'; +} + +@Get("set-logout") +public async handleSetLogout( + @Request() req: ExpressRequest +): Promise { + req.res!.clearCookie("username"); + + return '로그아웃 완료! 게스트 페이지로 이동'; +} +``` + +--- + +## 22. routes 재생성 및 서버 재실행 + +Controller에 새로운 endpoint를 추가했으므로 routes를 다시 생성해야 한다. + +```bash +npx tsoa spec-and-routes +``` + +또는 서버 실행 시 자동 생성되도록 했다면: + +```bash +npm run dev +``` + +--- + +## 23. 브라우저로 인증 미들웨어 테스트 + +브라우저에서 아래 순서대로 접속한다. + +### 1단계. 게스트 페이지 접속 + +```text +http://localhost:3000/api/v1/users/guest +``` + +### 2단계. 마이페이지 접속 + +```text +http://localhost:3000/api/v1/users/mypage +``` + +쿠키가 없으면 로그인 필요 alert가 뜨고 `/api/v1/users/login`으로 이동한다. + +### 3단계. 로그인 쿠키 생성 + +```text +http://localhost:3000/api/v1/users/set-login +``` + +### 4단계. 다시 마이페이지 접속 + +```text +http://localhost:3000/api/v1/users/mypage +``` + +이번에는 정상적으로 마이페이지가 열린다. + +### 5단계. 로그아웃 + +```text +http://localhost:3000/api/v1/users/set-logout +``` + +### 6단계. 다시 마이페이지 접속 + +```text +http://localhost:3000/api/v1/users/mypage +``` + +다시 로그인 필요 alert가 뜨면 성공이다. + +### 실습 인증 사진 + + +![게스트 페이지](./images/practice-09-guest-page.png) +![로그인 필요 화면](./images/practice-10-login-required.png) +![쿠키 생성](./images/practice-11-set-login.png) +![마이페이지 성공](./images/practice-12-mypage-success.png) +![로그아웃](./images/practice-13-logout.png) + + +--- + +## 24. 실습 완료 후 최종 확인 + +### 서버 실행 + +```bash +npm run dev +``` + +### 확인해야 할 API + +```text +POST /api/v1/users/signup +GET /api/v1/users/guest +GET /api/v1/users/login +GET /api/v1/users/mypage +GET /api/v1/users/set-login +GET /api/v1/users/set-logout +``` + +### 확인해야 할 내용 + +```text +[ ] Tsoa routes가 생성되는가? +[ ] 서버가 정상 실행되는가? +[ ] 회원가입 성공 응답이 SUCCESS 구조로 반환되는가? +[ ] 중복 이메일 실패 응답이 FAIL 구조로 반환되는가? +[ ] morgan 로그가 터미널에 찍히는가? +[ ] cookie-parser로 쿠키를 읽을 수 있는가? +[ ] 인증 미들웨어가 mypage 접근을 제어하는가? +``` + +--- + +## 25. 실습 중 발생한 이슈 정리 + +### 이슈 1. tsoa 설치 중 deprecated 및 vulnerabilities 경고 발생 + +#### 문제 + +```text +npm warn deprecated glob@10.5.0 +3 vulnerabilities +``` + +#### 원인 + +`tsoa` 설치 과정에서 함께 설치되는 하위 의존성 중 일부 패키지에서 deprecated 경고와 보안 취약점 경고가 발생했다. + +#### 해결 + +설치 자체는 정상 완료되었는지 먼저 확인했다. + +```bash +npm list tsoa +npm list express +``` + +이후 취약점 상세 내용은 아래 명령어로 확인했다. + +```bash +npm audit +``` + +`npm audit fix --force`는 major version 변경으로 프로젝트가 깨질 수 있으므로 바로 실행하지 않았다. + +--- + +### 이슈 2. generated/routes.ts를 찾을 수 없음 + +#### 문제 + +```text +Cannot find module './generated/routes' +``` + +#### 원인 + +Tsoa routes 파일을 아직 생성하지 않았다. + +#### 해결 + +```bash +npx tsoa spec-and-routes +``` + +또는 `package.json` scripts에 아래처럼 추가했다. + +```json +"dev": "tsoa spec-and-routes && nodemon --exec tsx src/index.ts" +``` + +--- + +### 이슈 3. 데코레이터 오류 발생 + +#### 문제 + +`@Route`, `@Post` 같은 데코레이터 사용 시 TypeScript 오류가 발생했다. + +#### 원인 + +`tsconfig.json`에 데코레이터 사용 설정이 없었다. + +#### 해결 + +```json +"experimentalDecorators": true +``` + +--- + +### 이슈 4. 실패 응답이 UNKNOWN으로만 내려옴 + +#### 문제 + +```json +{ + "resultType": "FAIL", + "error": { + "errorCode": "UNKNOWN", + "reason": "이미 존재하는 이메일입니다.", + "data": null + }, + "success": null +} +``` + +#### 원인 + +일반 `Error` 객체에는 `errorCode`, `statusCode`가 없기 때문이다. + +#### 해결 + +`AppError`를 상속한 커스텀 Error를 만들고 Service에서 해당 Error를 던지도록 수정했다. + +```ts +throw new DuplicateUserEmailError(data); +``` + +--- + +## 26. 실습 인증 체크리스트 + +아래 사진을 `practice/chapter07/images/`에 저장한다. + +```text +[ ] practice-01-tsoa-install.png +[ ] practice-02-tsconfig.png +[ ] practice-03-tsoa-json.png +[ ] practice-04-generated-routes.png +[ ] practice-05-server-run.png +[ ] practice-06-signup-success.png +[ ] practice-07-signup-fail.png +[ ] practice-08-morgan-log.png +[ ] practice-09-guest-page.png +[ ] practice-10-login-required.png +[ ] practice-11-set-login.png +[ ] practice-12-mypage-success.png +[ ] practice-13-logout.png +``` + +--- + +## 27. Git commit 정리 + +### 실습 코드 커밋 + +```bash +git add src package.json package-lock.json tsconfig.json tsoa.json +git commit -m "feat: 7주차 Tsoa 및 미들웨어 적용" +``` + +### 실습 README 커밋 + +```bash +git add practice/chapter07/README.md +git add practice/chapter07/images/ +git commit -m "docs: 7주차 실습 README 정리" +``` + +### Push + +```bash +git push -u origin feature/chapter-07 +``` + +--- + +## 28. 느낀 점 + +이번 실습을 통해 API는 단순히 기능이 동작하는 것뿐만 아니라, 응답 구조와 에러 처리 방식도 일관되게 관리해야 한다는 것을 느꼈다. + +Tsoa를 적용하면서 컨트롤러에서 API 경로와 요청 타입, 응답 타입을 더 명확하게 표현할 수 있었다. 또한 `success()` wrapper와 `AppError`를 적용하면서 성공 응답과 실패 응답의 구조를 통일할 수 있었다. + +`morgan`을 통해 요청 로그를 확인하고, `cookie-parser`와 인증 미들웨어를 통해 특정 라우트를 보호하는 흐름도 이해할 수 있었다. + +이번 실습을 통해 Express의 미들웨어 구조가 단순 보조 기능이 아니라 서버 요청 처리 흐름의 핵심이라는 점을 알게 되었다. + +```` diff --git a/misson_practice/chapter07/images/practice-01-tsoa-install.png b/misson_practice/chapter07/images/practice-01-tsoa-install.png new file mode 100644 index 0000000000000000000000000000000000000000..ad63b581a60d697efe8af91c8380b9f73bd5f78b GIT binary patch literal 5162 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 Gh5QGnr3-BU literal 0 HcmV?d00001 diff --git a/misson_practice/chapter07/images/practice-02-tsconfig.png b/misson_practice/chapter07/images/practice-02-tsconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..aefb5432d0a614233579f27f3984f473b503e924 GIT binary patch literal 8424 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 E1L~QA=Kufz literal 0 HcmV?d00001 diff --git a/misson_practice/chapter07/images/practice-06-signup-success.png b/misson_practice/chapter07/images/practice-06-signup-success.png new file mode 100644 index 0000000000000000000000000000000000000000..ccd4eeb73530b847ded6b35027ef8e644edc0ada GIT binary patch literal 7262 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 literal 0 HcmV?d00001 diff --git a/misson_practice/chapter07/images/practice-07-signup-fail.png b/misson_practice/chapter07/images/practice-07-signup-fail.png new file mode 100644 index 0000000000000000000000000000000000000000..71efb968dcdd1feaf9eaec6429a428b048e611f3 GIT binary patch literal 22681 zcma&N1yCH_x9&YaaCdii2^QSlg9e9zKyY^pE`i_>+zIaP?jAh2yXyeo@V@_Z&$(6i z-ukNO-6K6i&+guPuk}2?6|SNrjf_Bm0001xWo0DP000P8@D>FJ1AfJHKDHLTgK$!l z76VjF5FLV_eE2G=C<*{n#~^}?p~27L?PYYF005NU_YGput{4aaXtT&lh^o6Aoc{Gv zSJ%qq0ofU+1yhRGQ6`5`U|W93H{i7Mu2zjwAoSpbCl=*X%mMOc0Kcc$vQ76*2t=e- zXSl<=BJwffeiVhkWa7l@7W3*dvas|~FGTy9-QUllSI6EU#D>1t=9m7QbEGcnuz#;p zI$tv(RCJ_H{kr;m@_D`77%v2ZJm{ydgQ_iA5ZV?mLoT@e@ITrhbWI7w*z9SMJn)~a zNKQ>EnwS(GzhoVB*2#qb+a8nmOo=t&hg+_uwmT~Dm-PZo3u-dnk4dCh{;upw+K-}4 z%q?7KT^ntTcXM+?$8$e3I}26nruhhK+~G(_9u#H79cfWpjmIyLCFQe4WEQVT@B;=g z_4jYNHHNdBn^Uigt6_8EQ1qx&5B#pH7#x18HO_RZYIw2o96iqpmvPSr3r>R4;JVgU zLKYSldxz@B29c@wrgUj7ad`5)Ksm1CPIye3uKs>WO_@{dsU8ViF-vTFj>w7-3^6I# zV{9A11$1p|>xwM|w11)w5`T^}k~?>-XKv8X>26mILV)*XotB8%Hx^@GqFaggZyYcO z$R6uRoO9)Tss7}C_zg?KgRuc6L5zwDa)~pYHYf_CA@ab!cs+3A{=15zWC9w8953$x z3B{{uir<0GQ$Geda1KvLP!;@B%%n7x6TqE?|4)wxp{q)imXsuLBYdx?*?b!*jIuJ(-A&&n^|P^RLkus-Z9d3J9%nmUXK772 z$?-sK#CSl1Y5*%wW=W`T%;Rxt_b((v1Uvgn3HVZiuj3Uji07+)47pq*X=U9p+RA(g zm(8aOl?Df<)JWqY>o_}3n(E-j1ycaxii{C`C<_fNrdf=4zWiJL=H2w}-#F_J5i^N{ zviJD-{lB6N8X_&gT|){Xv{p@W%Ris@ntZnCaKQ8{@i}%A+XY~?yj)dSjvT8C#b$Xd zdG`2;X`aE2M9%zV+A@3kwA=rWqoMHJ?(wCHdmq^!X>0D}MkC)gQu|tQPb|&+n&*Z7 zRth#>dXl|k+eFtFJ%= zU91#{AMbpu0A?cHO%Oo+m~N5Sa# zNt15HrX_fL9dO-C+7IFw^}l7RcVM^fM%IlE{q2-E9#o8_%4#>3AYjum6LyrnWTHwu z`jB&*qurGd!mv}--a2Lm(3)eKwm!F0)Fq?{ob5BQQ0T$-eQV$@vy2RuR0Ted&_1Ro zW||Y`<|cSCJepj7wQMpZ>*y?CgS8Fq^JC*{EP%+m6x6}2fZO+k8A$dI?%9{=4XoxWJl zlgr@Xi#7}JW|op_N$aW&esA)4szdX{Cds-~{HqJ`3Q=}%zJMT#m#1T}3x00X7C%$n zWcO&oz%9jO_O~6tPliC^Y95`1-}%FYl!^<1|q3s=mWz>VDEQqepwSN=rBWaE7LUbZIqeltq`%kxxgCr z_GP6DfN}`0n)gLQzLw)6VByc8C*fYgqZeOha3MUQx0+~3+N>w2Ra=d~| z53_tmmLD~!IqqBQE+!Oe?+;M?Oi0J6YO&!gCjxc~(_}9pSE5t$Gg;!Zh`0lQ_XewL&Se!MEw`4#aN|3pXw?g%O3QQMy zpAa1&vHxFf&jU*?CH>M`g{WNIzAfH@1?SY%Brg2n$6E@y;k#|IAMlGW%upf5D($gn zL$vv<*0|HYR4X5m0y8s7cJ~ZCJw4eV&n`(#pGY6j^V2FZg*;zfUn8OQgLHE z{Rr+5>C1ui2n|5Q&5cJ-Pj6$3V*Wh#F|yzzo9EH=gdk?G&@X%2;m>+qGE93K2lmWN z$QgR>utEN@fCr8IP0pMX8@jB2)5?Ia1SR#*rvb{M7FVkb2WSGd|Uw z)m_|}4^S*Q`L0ZdfluLc&3NG1&N7+#ed9=Z5So`Y>OKTu?cr8;)n4d^59C&gy6=HW zqu?JIi3mP}V4Hc*Y|5lZ#=3a|dr27=KLq4@7k4A-*V$v{3fd)k;?=dpz7dFG2DUEB zFHyPII=_?6eq8anSfVh!umhTy6n;nro-}=HJFHv{Kr@Ni#%6Zr&V8;@AvR=dZEcm- zg4l&FBv(s1vb9m9BraSedV?`?^mj}%rGdV`?&`Sm!~6qYj}w04MhV`I5W|t*EtY*N z(80f$BEd`>yvOb za4pdvr18F>=aZ^O9;o1_N;-q+6p*CGaaw5PWc;FX*}3tVMEN$#v!+;+;0{rb!ZF0c zFpFvB;K-G-Dc)PWMoCUeO7Z4!G_fb^Rh;()A5I+{LiEA^R~woF9&mL0C>V?YCj%j8kH&;?aohY1)I;IW2#em(H^@d>wTr8)1ZJQ$ zKUCgN;O?P4Igz3#b~SheD|9jlFw0GL_u-wzs7<BIf(M$%lO+_0fR#zGJU)DuOcz?$U1*?GFy(MZ4-!GlSRfIFggNmxG_&4&ts> z`%qr%r~s!^vt63dGMPV=j7U=h{asi*`b*Xts*g5(gQ1AVEX($GAGm!d@1z)+hJK_3 zstXYh45G3=uSu6ns(IgsP^x%;N_|a7#ZOg_1a~s&mfVrm0d}4&OgBEBAtVmMV}YeR z+5ooLDPLbhz47>et=rE)7O0KLe#2TpNz@d38=f};J+eA>c&&$*m1VR?6BM~7s~_a4 zHrlhU=)M6W$MP@_b@j%IgsoBVM+%xIPe~4v_3--!BLP~$b;%4kFC{?1GrRVo1|Jx` z6&l%EZko-u9q;)zD~yRj^yMFajPPKVCK+{MmYL_cTTEV&Y_&7hP--XhQr4YG>j7hm z_vpN)3_SUh(k*6ukY8OkuM`2sqnGxT+TO#9=B;6`hvI!(TtuMMngAq(Jc!eJOFDE< zH_y7>pw{*)ksCNw9Vgk;x^wMczUNrJ8sL6M5UdkR)P~986?%L_V)B5v_%fW4=SqxY zi!EMQihy*mdY7<>sc!}F%s?g23;jW{93fn&wY}VV>c4_XZ|_9`T|MZ}YM>aflJ+Lu z{1KndA&%r%ofh4nQa8GSq`JxhIRrod3At)wJdz$4&9hB=^CJ}uuOvpbTkFZ0D?yg$ z7IHNI`32;99{xpxuf9lXK7E;t_=eWtND8!9+TP?R?v1Aj(fMG(dUpQkm&hLXX0P#&zPr;9n371{gv^FX`K zE?E!u^tfCBN}xLb)2sC2l!a1$8Pd z7v1Yln3Z;VktND=9>?{t2d`YmLmoiS9^_S02QP z)_Fw6vq@8UVW~{iZwZ!8QdSBq)X)12jbAiUio0uh;c2W2YQm$~P;jTaCBT^m{l~k5 z%&vlU)Ky_D;Kd@|1g2PsGBS(CVXO$WvpRS_8w#t%SW@7&AQR?OL8lHvqy2xhrjVqL zCt)2YD}U4jP0bRyNvRcAe>^m-mUl;=5Y%np%zXCz8oi}PZ|;?2PR!*jCm;Cd$r z8-@E;Mwj|q)#0DK6nd|OT#05~!@upp>-m0>rkasDA5qM)wz-z_kR*Bde>&xezgpF1 zvaGhR1SGW*TQtvxC*85!O}g>hYkm1l)lq6GeG0WZ-_fe8gOKs+GzJ=+WOrJToLhcC z&}B&&79Ra=4#+5OH`*2WHBxOPj{O>C8HNOixbqE%9<<576>f^?TTtHmgQ(y2SJ(aa zhp9|iQ{`&p?O#a=yVe8k%jYwST-cM&Pj7FTKl~oBz%!GjCJcy2o}_g$SEm`1Ts#-C z*{>}s>EY{TV<796ou#nU)A?v~mL9_k>X);o?cNy8yvMvh7O!-0v2MQp8A5p)6&<45 z*ys@&=%R2f3-DUX8&~b5rjinLnvy{jGYg3;vxw|gI2-L;wVOVtLT!Exfb(*A$^6zW zXow9vb0S`E2wfDzt|TkWi_%WJuW#NDkXNUmU>7+pY6JQ6J}=Vtu)R0!AF~jO?yq6D zozoe}rw_Hy)@_aE5Nm0(fy!2A^ls=_#8zF~R!N)fUm?-(`AGTSr~xrqWxd$D(SV7s zTHS3&U+KK4kxDZSBWipgn$lDQu1AO=HvD}3n|lX38RXusp~m8iwyichAHQ+>)nUbs zyubv=q1$xdHsC+F4|>jB$24XRDD2t>X`2m@WWW&oB^mU@xO4tP*62wvAKY3!=@a<# zNt@9TMld@MRzIn9v8Eg_IrqHPN1D9l#>03KZ(~LmhpbE5ok0J2Q_u{fPzg}jq8xmW z<<_M1^Q0#YBWV0MdAoN3$4EZ~S2Jj$uZ2svHx&GmSsJ|_YOIs?(rn@##_2JGos{Z> zb=fZ@047>f5AO2Jv1TMOz-WfbT{#uO&drKc?B-!kdg@YSCarB2{(49HqS%>MN%|{l zq0-(fA*6pPVxjZx4Z<0TqIv`!J`#8Q$`4twfkN_HS(ywT;FYD!w_{@2+0%l-D&X*4 z1dvYm_kEAOzN5j_yENZ&~kyuFbS?{qI75QTdib!oSa9I@Nh4WK`NCzQLx-J&Ixhb)2s ztmh5P1QQ%u+$kZhO%zK+-{_I*Np2nc^}ON$^c$-!dsVGVFb3!Mj^v0Ktwa#-1+MCM zc`ua_7c+CN(8+xnzjd@;LxBqA5Ec_;FFLNFI^xfOqmK+@-t|bjWj6`?{V?PQi&@#v z3npzzm)OpPcH~!DZ!v#Vj<8EX7k@WDE~-sO;aqE2FKI^pG7JiP%YYrtz-h}+jUvV+ zqF(^LdCkVQB%>x)D;9(`Y%>&YwE$&0bJUtq@!3>i7k3`ZA-eWGwR4xs6uDJ@!|x|u z3}0IlyzHFLP(2o1Z0J{%Qy?!kh%+Tb0H3*Rs;e#*Of=Nabzt=9!^~Dt=_yW5zWkd? z&mI{T3OS_$7DzTagOLRfjv(|jUQu1wK1g)b5EPz{#$xedS3Ho`tv*8Nr})#qUROJL zA$9mjqW67SkBA$_RB=>Q(<=hu5zc=w>c?`3f4t$+^Q9po6(Wt$n3TwVXq>g*NT~O7 z;j=F&!;nn=b@4U+$6x^gQLFRl>uy9ipoMzUs|Dg#snEh0?w4>xm)AVP$WHUa#Rfw{ zxuO64Jd@axU)cbu_@l{bbivz&sldefF=2F#KKVsyWp`4%U;lQsyec==Xr96&J9n9m zKUGHkG2?{lo8d(?yq0)#j&}sgt#ovrI=JKPGg>%h%ZQSx46VAbb9f%Hz=PD$KzQ2L4EV&^y-jjA{K3jlqdw&-;`5GIZ_2-A6Z=DY|}m+^e` zwbQ}J8glB=v`m*YAYX;`LMzb=Td&k}>bC1l7`I)~{e!{hLX(I;??0P9m(RP(R8RR?ZYEG+}^QyKV!STIT2S#2Uf2^-JR^n=88Z2&OG@{`K zjbL}Kt3^SXC0CoRn1LRbRQe+Qps>%#^_y%h2C@>zqum|pf87UDGSU;RkI*pf#C%UQ z5SbKApJW&)942>Xb>>s!dC0!VV6EQ$b$e)c`q7E)&H0l7ziMDE;_gHLxo-8ArPZjY zQhytqVH5Y?z|!c9v6?4$$CiA&rR&zv72}4S6A*;-c=W1Ha!urJ$?t1^Dt0-$7U4#nDe71iF>6T$#>M z{BD6G;&QnY$7ruqdh^n@ze_J(%eXa6lO*)bTz0bdmY}0;J9;Tv-CWZ?vqHu_FENu#{ubj9YwTr{Jo4|J^RP8?8WxZ@5=A0P0zj^Ap5nQB|!8|V@(>7(Bmf+y}eoQ`9eG193e&euCG0ofm=)qF+ zDhPR5LHyvtU$$ncfhmig!-PnnE<@K!;E|1swYXk%aIw%*rwxd~5T%p;aL z=q<;MOyP+E!_;wO`j|V9AVJ_REK%$Qkaguo2ZxFXv}*Pg4zGzx+U}_UpXIt%-s48( z!-`*7h^3YGxrTasmc&wr3@G-^u0sg2aO{u8I*IwSqnac;&A?3u?Lvnb6!M%Mq-EKmQMh?9xm@_v^`*M;aYNsiV-PoT z{Z9^gl-)<8D^1J_drTRPDdQ{G;T=_Nls2L5xSc!{Ba+3OS7T-61n&yz{6w>e=b|i3 zdDkW(5`PW!9PQ0YSDVNz`edQiGLWRJ$|S1A=^rb#wS~hF?8(W{#yT2Bhh*ewFONRN z*vX*n_NK=$ZxFhf6)W+*XQ$ujUzEBbTkXbubPMq%-G)8IY6FpHJAxX|TM3)L-*%$s zZbvV3PICH{3tAQIcVGFJn?^;!S(>*(^>yN}H0#x@Z^}N%ER=AqjaA0M3D(L3LAf_X zJ0Z1J%`3CXK9}z?jOEY!pHy4%nx^~lBjp(SjVfx$pc~l@kr5B0yqBf5wf%*F+!I7z z^W*PzerQ4a|PQLiliREZJ))OE)flc?p8Y%$~NG`pf)d%3Oyr zJIHh`=6c0BWzfv;3F}~*g)=jNeJtpcH{^?EwlR;}OWY)4xlAvWJ zUVL)$d{N@3dcx8SylB|X;n5`{s9reyG;6KeBoy`a=Fn!Vg5f)zhrR%nz5iv-{Pr)Y z#oEjM$b#eTCqVW27vf0e*S;&A-(YD#F{7#w6JUhzy@kb(1apSrFLlji&qO$fN^P3z zDT(-t6=k;>_*~kv40-hMed+ICMd&%ptKb6^UBZqP3LT5Hsv@Aaa7yMROfQ>JN_&%FaU&r8;9zSd^8PNe?$ot!WdB0XIM%Dvn7}5#Gf!FR8q5LP(AJl$? z!KKd{M*f53LbtCJAWzBfE7RxU)K%SM*YBFo0Ov~L_^~Axl1PmpwhFA}g7Kzid=s8N zMBiqcEAigJj0#2F;l|gQ(evY-BsBE9&1JDQxn3V~bVJm(VsY`5CbeS0y)U{BeRtS2 z=9B?6i9>ir9Q?x07OuWb9Hud6S?IuAt6ZRU9t#CN75HA6?5@3uq~;h4FRwZyA|1KQqx0TjdN`So@h!3p#;{zL~?}I^^ z@~K)!do9%8oTH-}`g7C;A(|3&Zj@Hz&yG@v>vi+U*4M>y9rh&tHNTz$fcmjgPoHd6Jp@P)i)aDI8 z1U1nuD1NLTZ?`Ca+ee^Cn2LG+=I54)V8Dlp zlI4vdVxg%-wp!!Lk#Hogd2uo_mLxc{GmL7Twn@R&3War1QC@~!gd8e5jnst=a*}=K zPSBv>Nk1*XmRKF2d1ZB$WY4YsdE9<@$@VG)Ia3Ll5{--FAbGIoGZq7IWLy+B9Z=sb zy;lSuhR)FOwFA}hv9pSM5>{l<(E$!L?y-3d_hvNaiM1TTVEqd_BJ>fFy|Y^0UEax{ zqw1~KTkyzS4>MvpLR}ESoQsU_97jbp%0JEN!rqRzZq*^poC({r@zQJo7)*apm~E6r z%Cf(g$E_FhJ&Z+mv*X8sBkAViWAi@oF@C8JYzeqS3otgkw!wO%eew=zzdp0ON7j!_ z&(BaSp~tFhrO?xcRax{^mtHyU{OS_#_eZC*ahB=D(2%Bvk;r~SQJ=x~7bv3=OwVYN z^NPS2Zh-BSI3ctGGRnr6r>r_*3UKDj81=FZwx5dImmmOi44prgSJkQ)u3v5ZpGWg< zUAFIyw641o%HZ z*jxCJ^n1H+2eXmchujeCa}HaL_cyq27&X7F73ZysvWnTId!1pT%t{H%rh$#SO}ETQ zcgya3vlzs5h%wZC;rO8dif>|`^cUA2bBM%|eO=~f-KwRJgBH3dj9ozSGfM^EUXSQ=zA0j^9Djaf+SPQe`qlJTC8k^XMaMbjZ=!(pl@S+l;&t(B53K7^ z+ieaW0hIJV!Cw}SFRSYu5UXoriEEUjKE(vaHBdAe`&KNrraed@8mtZ7*3C_X9%IAo zxQ6%YcWl5z16gXyZlb|HG9AEA=o4Jir%=MeT1?gH4Hh@J^GTd6?9Pi`vrUdJ52IK@ z?)J@tvNz7`bm0b!iV%4Z^L*jZ?f$?~@$hGdU+wRy^7~7#UEfNcO_P>s0qm?)ljge3QWN;cPc@@t|-G zlR790{mS6r0(+|ZJ&Xc|DM}yV9R~mZ46JzBv@I_wwcZsv+9NIM+F0KnH+D3p-Km0v zUZxlL&9FYnNqU}pY02vCYL~pdXNTx-=r-Ku!*Qp1KbS}K;mEl!gbhzJHv0BG9Z^d= zU-n2euQ?vKrKOBH_IK}}kha55DV9BPh6{D*VyRZ1lW}IR$bbqA3UmJgEIC;bGfrrK z4?;5{+<6tq)H+@&|3;ExH61fj96$c03LziuYr+b(yL zDR;rAAAH=iTR&vkfSIv<%fq2vH9?+cX-rE7%?Ife3^B@$Jm;aJ1QI>^U%Ghv>_gq-MtsE-!XJ z*h_hLVHpFc8qppQ4<}iQDD83Vo5PtCmYY%x*qz0s=oiTyZ^l2d^$Rlm4W^DPteuh9 zkc~?&=pXe=oH3|PTu1PRE^yKfOx#KbNn0Gtz?r=OxaxBNCXo;4GBQ*~_bxhG#YnBgn&q9jwzATnL5-r9Q zFQhs3vgO33&u3|YFL8Q^_@2+y1aF}3%w=zm?{#w3D#KIX+i&+#rh)i}c3(Ol^h3_jR7eh$-_JY$q2-Fj6n^ z;9DyHUgJ6&$S>~wQ$Jam;R3EYY#oyyh=|k>DL2mD+A~& zg46T0VL~5(2)&O>blHD|2GeDAovKpg3vd4=5l$VxEs$$sOc7x)k0agoW9btU^z@OG zC|^Nbx`157#xkadIyBX5Slx&K=>h%ufy!+53#&n`G{l7#OZh%A7S%;3?;&DbzK=l> z%lmaimV#P_x}8GEqJ;HrZ}q1{ODm)L5dgK*J$AWkQdv#+B2YCg6{B=;Tdf(S{Q1O) zU$&zd#GNKKSzjUZ$8;r1SM12gyUAT|er06AtX9J?=`LvyKTWdkQI3*KH$^G*Als`+ z&Pjf|5A135-u7A{*UbM^VENfB7HXB5dDVaPz?HPjVshoWYrh`UI>5KlzmK+sDo|Y7 z3w1D+oET06KbjC_0%n0s7PQQh{A0hb;=% zSlxo)QuzJ2=HtX=moS@n786))j`*hUNAK3y@O{P~>+LzC4bcGji+gBD=cZ0Uoy9)`VLVM_6AOr1c+O0}R+@<~iqYvl z)`GZOS3^yjV?!Tv!Uj39E-F=xMQww4$^UqbyROJyjnuibFJc2Qe1-kV&0&+@J4byHyO-%}5DjRS;knpCc!B^XSG5HRR|nSMGiERbpDAxPR}49I-R|6Ub5M@X2K%~fo70{%s8kO2fFCI-ro4a|;0ovI$!4lkPtlR2p$V+)F zxw^zg@unrA)kv6l&HoJ2p~gp?i?rBvkgXA<=8e6~M$zSHxoAC|UVQWhyBAe4`C7RS z#u~T=A35!xJ;*JO-^~Gn@yez=YTmGyYw1byLx+C(_rwp~MlkY#vTM*A%V31byZN41 zF7X+iqbr0HDL+{@h2s%6WyM#;`V9hk{l%X?oyV6f~D^%9(UatLucDEa+ zzyfx_%|5L`V_6Io`9bbAH*Ohepw=;FnTFoJam1TIF$L$~ zj9_xb6TRY8nWJN>Os@qVSCwypUPd$X<8Yz?8gbYxpy9*AHDwZ5imViW{=$w>2_Ab7 zExkGWI5V|;Xq~>~^Y!yv^%2h}cknS?RwGmj*a)V`+_E&`zHwKyOp6cTO*<;9jU`oJ z*)=xJ9fsX!FWHsZUf!;TNA?J5DI)RY^v zaKWleHEl~_c{ytDBgDKDVKe{eh1YuXL&lqPzU@WeBWnMmW4UxQtv}fbC@h2N$+H=; zkO_64CbFm6xCW6s-vLurH|sl&aF=%r9y;yc3u7_B24+YbZp%faVn6S^`Dy*IcEJ}3 zXb@Gry-z}vN6GNrH}4!Dbc;o1+2VaWOUEgHB_>7t;5*XHp2kk(?~H(l|7fSPR(fkZ zf{hMsju;(7dePYN5Jr%YhVD4yvO@0Oj&BAlZH#yXifngtjJiWIh5w3HPB5T*SEJr& z++tk5%wp=5mCbl>rA!sWjQP-^_5h_+x89zrrJwqD>02y*YowTkl(*#tVd+dOVf5rq z)hZ3+4qSx@|WY|aXawf+ zkDMa(T#nfiXu&>;l$g zsv*c!VLc^hs)OTw^w?M%Q^Gc4-&MUlVjbRx?n*1yrndpjJ>^8onXCc*$2kx?*Pz+rkto1m7q8PU$)C zy@n691WN-c;KgrOJI8`1FLV@^TP!I}_l$gTG>~xS>t?*B-Pm0|@R_|or4L()rQIMo zJDUy9l4do5ylBK@Poq7lnBK2l_=HPgQ z_P7u)`zti)e0^ISl~>zuXb%j^zaLwTRxK?cvUc8ov_2^A`wr#eqx&s2ijT8e0=Nol z9^(5iye9V_yhhB96X2WWFnqtfzkxl}m=QqD3NT=7UYM@v6_drW)ChpcSjmhF6-+w{ zTB9pPu|5BCXo*rk2&!VR=JqYQ?pOA^qN9O0IBE$6QwS+o9RQC>1x(irj5a_KYQ;e4 z=GjRIi5AFNk!l3PdlO;fGTT!7h`xgppZ`BWoUIM4n4!o^v;=6m6nbH0m3=|CnHIl3 z7o|#2)R!04;WwJUu94C&rS)Wk9E2G0h&iue4yrPMf-5JKXO%w7;Fw!ScTz7dFlOCb zCAw+0vHwNy@8&6Afc_54uG5%zW5QpaV+FyCHQGZOo79BvQ*0fQ3%mKce#W zKv@~}MANmVdBg0otr7fkF>EVWPr80|k*(Q04$-4VLTL!57ik9UV-g%?{5&e>Ln2J7 znQ2H5XHc!^LzOI!SDxg)o&TOMl)Lo?i+hq+E$Vd764Xl_;Hna#2;xjWYhQ&Wseu@? zuy0nT;u+H4G2y2yIN!aO15z*$nTrnA8$AWa7V?ZVp>iKHzkx+c!WWgn`afXp#b3n; zJkPv5FE+>6cr{HD!me*D$bt0>edLrk?u8^$gtkh$nug-GqNC;#HQ_9EseHx+R7y&PSP%gZUL8$n#g`CyB?m{UN4O8iymqL$G zDe76?Qr3uIYMV1got+c+^U3b8%F#YTiWMs(&nKW{35kwz9F~{CgP6`QpA96fxiw(z zL*$4ZrI3)`!)XimDq+Bn_1gAM1Vl&5J+j2fi)*kr)p4(C1z}{_CHi2ji}zOIgibmg zJj(O9-|)MmXr#HM#z_dQt6!aes7MhO_b&VeQnsdDnT(Q?@X1wbPK{#ESefykT%>Co zDyPvykH`QcAmh|)gY+mf72?*qg!4oZ&m|?NgC}zX)JxAlhF2v^*^lH>E-rACWK;4d}n>;AlZ2SN^eB{Ui~RV zdYE(wm)?(En0Bwqf2?ZJOzvy~t$V@3vvkZ=(phxsRNotWjn1N&4$D0d_Qs3WYGGoT zo>s>tEn|K4Ph#F3Fs4E;DE7oR7lYZGC7x?Kt;4J%1YBGd^OU3K9!HQ&nldG1nlK2g zbEx>XU~vksJAu593)z~mpb$>LklXjE1t7`rV&M$-4_948jhh(mn6&bbF5e~Rq;rR* z1>^ugU5D+73ZL~SlD$E2fys%aNeChThkN)?`mFXS6|Y*vYoEu=9q0VB-Ad&nVqfK? zdZ%5uV3o-X%*MZGk1~pbh>=8xT$m)_oTlRXx-tmgS>BvrdE0~-!GMGtj1KnU6j-Hn zDtqXoX`>4vH#ct~H8r;d6XQUMq@Zq%R)n_;f-M5>X7Z`fR-p(OYiv&^tyo=}&Iyai zBd?P5Qil3#u$@VJTwjw6Uxuqq2xBD~pepw9Im0J{PQcAtNw%-j0cYAvu{eo`;-A7J zk9O+=2?HgbLHggJy%=IKqkg{+`N|;;ynM0eUrQppg7`yEr z_Cs08)}NB0>-5C>lH{Xd8AdPm9l%bI4-!6kpqSuN$G;U*sB(?865rO|zC@!g)W(cE z0h}R(^{H*WbRK+)^%m+obMKUB2|N>+DTKN+XA-1@R)+7&zoPqm_=itB2OD69EvAFy zjKN7iRlB1E`H(qV1Yb@dmam}3iXy9aSl8H2FQr~Pu{Jl{8U`)_BzpZ;p?+N@8z`VX znU7`2N0I;0FwsYFI4BAXjPVK!5b#5G4Mqgs-le`48~+pqn%zRtM!R(( z@Q#NlYnJ3)%=-y@ukH3_-u0tt?3C-#oaLUn1dUU?oxp9&lq=^p=VzQtzFyxJsY0gj ze&Y-Hvp*E}=-$3ER(i=i{TjKBCF*6nx(80mE?Z2>N};mBEsd|LaNmyJD;O)0UylhB z8;jlP8{2|-TI;XpvG@M0lNf{iw1z;htn2c3=jMEN1}0m+E(Y2{ z?OWAIy=-G(Q7>=Zw|CVDuka$!+Wgc*mJmO-i;X*?SZwz9WXe+3OS0p&KVI~fm}CO8 zx%5YZ=CA*jKX7#PtPTOAm@694}b2l8GiS%h%{0o4Z@0^F7$JR;t^_ig7;RW4%*=~~Uv5^Ya*>4im z+%*)`M`|o4)H2{4!Hgs>={#p|Ve)$^C3r~9&0AqKIEuc!syUjs9Kv+n!u`C*vA_8G6gF^={oX?;Pvpu11L#{gU4$L{kl-9${93xS zUwYYDAyt6bP`=UUhA55%)*s&9#Yf^110$yEkIQ#QN_^^nSyiEnFF&5%X-qRp{M!-) zq`2>{s`F_#@3#J-r#x6VeH51dSN0?KePOKQYvT5iab{X>l($WcTXQv2z|h1-9^S!e z-S+y;3q{#!I0td&t=M#Y!8C28$h5TZFJZj3J#*jmyy2A|XUkaHm&iXt`CP=N3iX2q zB6tLbgATktHWG!t#jaVkNxeA=T9>0O8}_1WD&&l<&J2o)`2au-M^jbfA37tPw9CxS zS(%pxm?>~^5mDn&$h|A4Q~FT)A;{QpNC4g)LjGuGxq^e+Drr@`87D4KvuMNZ=7|o% zXST}mM1*ANh*4kdD}<1dK@I>Kn|8WpB4cH849-akS|E>Y${N<}A#4O#&(s$Q;C#%S zb<9E1W!O0eXxCmW?YiqQDyQN~1l<{Y9%lH5#-|;-gs06@sb(C z6#ws9-hb3R{K_%h%jMtZvKO31$2f8(f-BE9-9Qr=p`J4x@1-5rGn;COC-8ZySL^0v z#rYSbE1zuq`ir>kcb)-V889H4Qiyf>#_dYFkrL6^o&Dd;g>vkF-)1?O+#i+0p4AoZ zg^g;ip3>t`BQMfMN6Mq=<-S6df(0&c6{{LID!p6-?aQ7zMDa%lVIJ&9sw`8#K|eap zX^QUPLL@hIqHCsc?HR_55+q%$(O7rZFtj+(8{Sz&>p=jwcX-M}r%?$AAPV)Whn%uv z%Sb4J-JgON9~ge9mB>tqK)n*biT+-(+O*z(FRBp$@2hL-(AkmI@0EaehlZY&8X-~g zj#WdR%Q_)L3pVL$gnSi(?ifOX5MmxkVptJW$%d`>NWM74{LDUV4>5T51^-G5%XI7c zzWSr@uNt#Hhs(EJPj5EmW_~avn}_r3~C-`bJ2*H&$liW+k|9 z#ndx-Z}vj}Qm-5Vm$FUHRi0WRa+*)L%G-)cK344!?&?F_{g`ih($s!0?P1y8U+w7#Bx)xLbt z1HP>2DF_-wrP3Q3&sz3l&v{0O0aH#D!=-p;T`X2NICViVk`n<(Yz2X#pQ?uia>M_%9 zeHgtqe02r%jH!brjzp!zUcx$DN)$a&wrW$6@*M%RU#n#7*`yYf%_}3G!*r z>%pKU+VvQUyZ6fKOsT6B5dPP`dYn2oe|7RB*?v>@7$ekbSyJ5pF%~0KY?6|;&PKi( zM|Dz>p&&hIn0j6&)bW+Cxfeggm`|5^%xwl)E#p){u{34jO@A8Rd|BQ@VHNjQA@qjJ zR^OPr!zakW>+r+1Y-v-5G1Wfr_oYpk4>O6ID-N`~X<*)Npu%2@B zB_F&(EOWAe7BNEwZ!JPiA2YmJ5dqoYf;Mio^XHP1uC9zq);d5-y ztUIow+@(A2&>E>y{H$h49+`z=3Z>ER;m z;(CtnFpIJUrn?I&Cf|Cp%7w=07%C-XKjYf}#uD~uBtl%{oo8I~1CyWZ*FV$*uFtd< zox^=B&;7JR_LMcZBb$~|I7HKl9DM|^_}@+?L8t4axBU@{_s_L3VMZRPu6TLu1%*H~k3nKm zhKhnS@3@sWbgg2Tht`(p5o$HC;sLH5e)yW&hn%>eda%y7AaJmx}P-*Jt57pnsCH(tEvbhzVVlRFl$#&P$bFeMo6z-+Ek!E zBfb=EmT}~aD-=!pLppG})eLS4f%8PDS3lxs+rB(alPn8)5OA+ckylxeVfhao>T&rt3tfRVHjcqDTzbo zHPvd(9zXvd#XJ3jt^XgB3&yQD6s+ zMO8yZ04fe z(|p{ee%X^G5*Ip@ap5h$;;$>@3SgVoZJ%W*R-*_o5(i~`S1R{Jl89|sbeEypkT6xx z4J3=dV}m`?D!5VA5d2jGhcpD}QBX)N{Lemk=aec%{kvLeLr{i>mgI9Tqf7ov;51(z9)o0 zHQfb!lqxkV3W-+;-SNioR-?QG~)KQ(+txwC(kK8@MGE1lUm z5^_6dC3Ij&Dm&#>T189CDcx@hgE=sZp9&}VMxPpE&MEiz2=jQ%)z3aP%eP=Rq7dFm*EVdOhULAjQ*o(UR&LiYW=GDeN3T~p4%k^RBtQ<+vy8NO7 zEc_ZDGf}!j^P;{dpRF#*JOuel9lPK5)I#mIJ%7X(uSeS9p?o5HsR3qZkQ!*5d@FrX z_NU2%;8I!^t!u^Ra&iuYahI(uGfOG1g^gj;ba5TsN3=}ul6G$&XJ-8FeA@<0wT-bO zl*RElMoVY9gV~p+X3aoo+?d0&_jprYo;)TvYBZ)g-&@;qGy1^Fy55Wt$Y^<|=qG#% zDSG83k0%FB9}N`d7Z7r}R?XcB^ienr zYkIk1exyMI?C_#%4%(|gV)&j*`J~vphISa1Xldif)&LE@8O-J%{1BZ^9p5JwrB4xh z2ZvoIPzXB2u00Z!=c7R%?bOrXpEELIj6|{;)Rmgj^If13)P);J=(Lf;a(dmUs76Oe z+a*o~2EJA_f#~I2Y$K;+RZyC6K|C~?;)pdo=XZ(8jglGaC65r+`RzyI3da5SqIESU zM^c(JT{(x-PC@XS)))H9{9#FtQe9VOew_lxha50uO#PPaCu*PJ+%G--8HX6PX%F2Q zg1L}&NMakeF|_|pV)#s68qOb4{S36NBJB-r-&WKStAMDmU=`F6U$;^BA$biIZY4u8 zZl!!P#B0jlv{Yp(-yPj9+Bi6Q|6`EU9n|=`n?Vk#at8`xRMH_~(N5|89fh|{=JLK( z($*rythguZ1*e9uqLmGAsxL#uTiUyfmqLRd4ZF|brndP6HPS~R!E;Z2RQQqyB=0V# zd#T|B+Vek~N6b(H4d;{q8>ynAqBZy_7&c${~f)U*TeA&;N$jRQOx!DV=^>;R=z8-b(U0rWko_rH^J}0!Ew7i^m zK)ci5#vdTTEsmp!ZZ>@hz%^>)--4 zfIJppL!qX0rewN)Q40loz8BRqZwn_GCAoT%pZx+KgQoc{j}(@U$KP}iFllb#Aor9_ zuN{KuYzV{a(DUx8j}ny-6(0pshN9Jg+&%DY@c9jiyUm9>NrB|!d2OTRTE~vmlBXCJ zj)aezw~?vU1)Z8W+dE8Uxu{xIyy(A{r?()JcvFd4#nu4oanoU%j~DFH=O3J4iG}tS zp#v-tm-{}(!AY3_H|kop)G+;`OV&NEp}@SthA`iHKJ^C ze7=YZ4p#~9D4X1jWG&LNRRi#VNPPBfB3R&Y-@GJL*GbxEo?I3AKMa+D8C^J zcX24YUG^$8N<1k21kD5l=;(SQw93nHHa-E$rV&6s{vTg~=430(Bf8p13knj3=T>8O z*D!c!?r^G-nc5HxsQ2oJuY#uX$~FGp#LjnLPi-xo;Iog{x;pt{=GivE!x!b$@ z7r#$Wa#*~>oRV#H%OAFyn)~V3Aw&MKRZBYk(bV#`tk_(aY3BijKhAi_CBuF&hQAUM zN<-%L`GEAc&y!mZk{lRzLWMWr2$6GlCfkG-ba8%e$W|h%_SRVk!$?1HgGl9q4XNk3 zNyLY7$j8>|?8=0St*d|t=fF|4lFn9Gl~DLsjCx8DjwzpDy%-*87aC==BWI>FwHCOY zn02aB27;mj(XgznpAYqR!?E#(WQvvr$NZZCpdH=&lpPZF&ky+ilj-xhKv8P`gy<0Q zC+0(Dbl$g@&>>Wv&9dk5VK2+|%W3)6epi14h^Ff%SSHOqF+Lgd;3hi)UmfADB*zM; zgRm1)m+ZC8i$@LzcI2r_!s(6>%?PYv)Egd=XeZ4Gp`0vh7d$W-<2juBEhGo8u7qW9{ zff5qKh+{S5Zht-}xpOI&y1`rlmlSF6k9LnA9rHLCDsP~+dl}T%9%3Da4CwgMtW%eA5~)PiEv_AZu{t|8EoHN8NC%lt z55|Y}V3e>!9xUNMiO>uAV+H*T950d`gE{P+X#m}>{61!U#D}$@Ql5OmzI5`v4o-5h z3XT@B^c2I!pH-K<>+-*IT~|2&m?{8gG4`ZRQ^i9o*$c z+Yr)+1j;8`ERyk1;nmoq! zuE*#xjf?Zy1Na3PP(u%bbR;);kq;9W-J>&;EV|1lPw?@+Q@ER{e+Li#RvP$6M&L(Y z#R0}+Xp_HtPrR==jKLDb_IJR~{_lBP^ap^0Bk%0MLE#huL}5A7>gy0}E{%VA2&Ijy z+E8`*TAbE088IWV*(j7@#lGgDP~$zpwApCsbV>0t!Ft*>!kH0*E$|IGgk(gRiiA8! z!pzXvyK1FjU(&H*p1IyqB0Xfcg{zl46u&J6JZ@%t zU|rbAyxAu0)l*CT&@i zDq2L5G#L&-u-_8jM|a*+(r|_{WOIH{+KYP_A9z5y>C5R*x)TCF|2|?cYA`6 zwN__?(^2W}S(@Wr-5`N^J8Yk&$j|%uwdRMFQ>#c=eQE8hJ_9-kr|vtD0sOPp8T}p0 zCh(cZn4dZGRXIylC0Y3fJF4J1J6J6M)(NZWB)CaoD+T@EI`{P3TMzaR?V2<>uIxp) zB@}j_!E{}TJ|?wWDkH}FujoS1gt2}(kvlcfFYaY>QgU0astnD~jLMef7jAdeUQeWs z@?GG}d>)w;ejZd+K<9};6emaZ?(VcxjWFwYW*!EDZnQ)Y=BL;Eh&$<1Qp$vff0C7G zAdSKqZRF18OQh9M{*72)@wbUeAUC}mEEHPCWO0S|=yoTLqN!(Uc0d6j3Z4qyWW0g* zU=bQQ-t#x307gZXjSWMD)d@6yTD4>dUf@ zSNGB~2F|x<#kEDsuSoY4D-T8a=v@u6lHIAnA zHi$=|L>SBU90o9yeyf~fqK^F6Cc%gEjG1-mgoFoTLtgT% zzGhIHEJ2Jczp!M*3`54`k5v|*DQ4bJFL7R8xMI6Fk7_fSy0hLiRXSWYXD+M#?3koS zx1))yS~&8HO|o}=0KQuGg9B%k-^O*Gks~}eU13p0aGDZ~%B|rkAMsRW5V4s#t~zi) zmrCN$$NHsF_0#&^ouHZN@LotDJ-W4Yus+7a@`}nz3ZB6Ff z$Gk{;62GqO+;Pcv>-4}w)3K&%Jv^G%_85Nx%(^?4KwoFG|lngw$(yGaQX{vwUd5f8?afw0^N8y3^I-s+i6)sU58X3;)y!L6Z54bRDjWa+fUl;is1E?3VE-EzU}OH%*Z>i_e+I=* zUqv2JIZku%&p~&V(~<)KYSM8Z?J)lNFML$Z`~U!gzW;QTU*2U7003x6O;OG;$ogc} z+lAVsQ|zotzm#jeb(HcV3jKv5L;Mi4Zak@5W;>L^rc9Ik`$oJv^CTz7wj_^mKuFPC zXLwC|CWZ+dZ8ZqZObVCBmqCt~*)(3n*!t^GJZZ%{TMT09XiDWtAjew+j)u(jaE9TJ z68Xi*ks`!RG}_#Iq5R_a#YN*1e%xly4_!#U>zf<9z65;R6ag7rB3Rxqr9Gfpd(1Ya zMw$SaBEa!qk@7)I=r>a0xBYJFJUbWOsb4}fX%S(*>GEU&j{c0+v6`Y%zM6s0VvAF< zJaAK+!vn9r^W$HP5Ev%(lTGB9)+%KOOOD-uurRo+RLZt#*~H-2fhDEsFxX0y9;Vae z_cP~DVGw?ZUo^d+O5igE$s|?yiyXtgbK*d%a4=v9JOlF6Q*u(`?n`prHyJN4YO)*s zb^hnq_$I~}C=ei~GKnykHE!}CwB2+A(NwT-(Z8YqoMOoP6!8}0ygk*nJzEYzxGznG z4ie_(t=j$-87lg4CWqVFWzA(WCt;z+fH)D4vB4rraRqBoONL6Hfv$@EDvY>2>F3a} zO29TP*Gg3Mctzi+Nh;C{u~+g`){PYyj1_}X7&Bu+c#`tWwP#H>!YW9{b9$A}88NVH z;0>3Mq?oc>o*Gh>?KW|JNW_b@076S@TpN`J6vpd5`jt>^TjPrt9!D=2MK|Mrs;y+O z%qfu5L;0IEibM2cE!FZ7}Eb?4A1UkgsQR}|ni zT*&81pV`{i6Ti`>ov`bm|W6Kd(y8~DET(zH{--d(P+EQ z#h)w(Tz@_hX~9fdQ?rYINMK((X^w?N^zY$yU;&_3ReO{46FAGt7Wl(ye+$p{X8(#K zkilQt>BiEZ_Jd?oeDB{M59k_4m8IaO{f1a?jcMy7nL%wn{)}W=eHFLwiEsXgBTD;R zqA2vGW>^sR-~&Fv?Zj_|8DW=0JXEVBJNH8y6@fRu-x{Sx>#F5Y`kkJo4hUA9|Yl^(5*5$hQve#NcvlFTfVG;8_m~P z^HL@TSM2HM6ft$CB_&Wc>FBj>wsUTGyd%YN6>glrwRGyks-bgoOd?*^G*b|zgUo(M z(Di>w3Vzx;(H6f>r)Gb?(@V(@Aaj~Rx)!#za;f!&#HbrYJoO|qpRY1IUBC1uuyZJ? z`2|?mK>Buz_dNOo?S>tkWX^d;2P-7N%#b?OeW5iU<3O=5`tvFq4~7NdrkQt3Sjd4| zD zRpl-~AkfT3@k9zgo%-*+`19-f-_XhTpLGU8_8)=woir44FZ+GItiF9h>K{q11g%+J^+0O6AvLqrCE$Ta|EDeE z`lMo9+6T-n(l0sc)O7Ql5&qgk<&i*0yc-Vi{bg}44Hzf$7s4KCQ+u#4=HDI_=p^vB zvDkm&h5ZCdl)HFbv?qX+FNUI>zi|tlb$uB%F%-D@K_u?2CmPhvJOI&|B)r7(YBPBd z!x@I5+^CgGWBMIx$?c&5ONOnRY(!$ZVFNjpb;#EsKPohF&NYhnPwV?GA|)r0R(4FU z3ky`PZkOuIYWbkIaVprvi{EBet3-+yclw6zD2eYPX}C-bxI9EH{-^~rP{{3nn|oe_ zyISBeIRqSCL4cVP-?h6)jIf}yVk%SK0aJ&}mnRXX`4&@i!K6&NIB00Lu@7U?yRTl8 zbAxL;NMaO{~ay|*KNXZejf{N_g9I+ zEe9u3C{Ig%;Mn^#zy9U;2NOE*`uHXR-)sa+4+vx6!83{Mo4F4FM4v#s#zkf2#SArR zb7Z?Nc7b2aNu!$_NZjf0BIwtSyzOSJ{1t`g7lgD<~CHTO5B+ zHm%uiTAvTab1H9Z$-I=|mp-LJ(WuGbcVrcaM4E6_{BJKbBWI7JTEmpm{#swvQr5m! zykt6F{{Yvz_He|^=4QGas_a^0`{IDwK=`cjcRMdJWc3K9Nx_t@(MYfUKc`HcTwSZf zo!kDwL7O|H!Lpg&mEDV*ajtkJUVn;^)UFGtMO#C4@#8dZ=|MK)HF#M)FI+{?%2ShE% zJvbUx2P%wAzKnB3%L38E<6gf}p~l4TLD3~Z7cj4Gt+b{&iy#8qq~kV*{HSW;^nsrY zOum0rpBvIio;WOP`6uT%Te;DBtkqi0mezMmR#m@M9(GZO9*pc$2KtSMRQ7D><;@iC*&ot@!pj?rwTwQ;AX@Kb|F0$ zNL#%uH}s*;RPI6f;`>*ETWS&0W!wR&juv-NU;x|x$p4mr^_m|aD67J8mJ=N6hd+`0 z?d@Nb8A9G}B?Xfi8v4Ue{lGrVdFlWGk`p9Jyd(z_L2oRJNHrSIW;TQ-XI4yaE3RLW zKGuGaldR0V#O^S7@)62}(!12o4Itp8jI9m46^+*nA^v!Uy$n`;a(Et*J%fG|<-{nc zK6L4}hhR7kdm*-^Hsk#~A|_tdzac|zWLuVzI^*MIWL-UHJjxPy`1k8$-@eF&^ytVq zVA7a^M$+iG`=@4s5?*@dwx1sUpZN-etupmr9R0f;6I1Ib(g>Y^vPU^hO`Z{BWv$DL zZK&80%;jtq1qDr5Hi0~bL6LgL#yK+mz$P)lgVt6^Q`_tEm`b>Ben!4>eu-Xf z@1+jw{HMPR3Hx#xyG$*?Fh7R;mVEA`#yV5N83s%uA{LHjLUea2d`nSKJnsn!p?xte!*Vc1)28 zIYYG{iD3j^J_ZqdF~+BbK-YDTm%Sd5;JF|~cqvel!d&Y~gzvRO7u&x{RV$F3iizzB zR%jp|BNaSca@}z_Y9;;V{>?h(u2coXWH(=fo=#x#)>pX2=a87mPM0e}6vfG|;lz!@ ztwPFn>G-wM+d4eHR%a4=N_)Pz1OkD7M)bb|q5mC}{r{_!w_-pwIinRhnVd}6GUXXz z@&gl%8FHL3c_*b6-2}JvJ8Iq0hS3!@`Ud7*2eYPsS9_K2oz0v#!-EzA?c6-44la*FnfJCPYRx6nek2k|5H6ZDblv-;@iCd^`cl+1AZ2!qm)<*+#i-2&V+0piZiWYU zO>hhs%KyOQvL*yw%rY&D`*s{(D6Cea^be>l3|a66xXx1v5!d9xT}ybQAyQ%M%>9h< zB@cG%4!NcF13KRK=D@A>Kc8Q&P$?y@5`>o52iAX@DlcurJ@U+G#a%0V*^lf5A$FbW zmrkmeEv8o5NuF{?+Jllw&OB?or&m&TugMn(wdwV?@AHx&`}hsHzGJ219#C>>TRHq7=_SYR$ko9>s`H%T~uM~^OIbEYs+rSGVU?KDT z>0t?~C5+xm0MEX<7A;`2JBwH4lh)MarnYaldL^CgoquHd02Ep>g!>P*fm0ZKDAv=m z7Lvzo7&yV>;|7w2tS1Gzr0m;-rSmwe*zbgpG{H@K>~DCpG{R>IbRW(iw}A1LE{ndb zznB@#DFcWbscqbghtjgsCibmk%_NnqsfQ#yR)i!rS-a&rD1_BXhYr-h#Pcf`e>fBXYL*8Jp z-`7=pN~le3j`s%Wf{SZ--(431S*{n)8GL)?&`joQH7DZb@^^Jwv6B)Xy}9D76omO? z>W7bn?WYms359VV>0R?#zcPQz-Ff(9aR4AXVBuOJoh@2)c(+om@9n@-arKtP9?1X5 zBh4GMNmUp^vVH5|C6Wkrfs)3s_w8e&*P7gK@x=$@(_RNf}f?$IBneyd3yt=LY3Q@3&vn1{jL*7Hb+=5i& z9086d%to4S%I)d%!ce2YhpdubKUg;F$q410O%rjJHdQjo)P;1aKSvxZ+ug)C%oxb7 zXkWgtI#;Q z6@QY*&$y$V@sl)7V$|0YNQ+@5W6!yoIu;-{@N`waRoC2XfIqGJgje5q$Vc-J_Ji<; zzGldGT4TZv%of%b2~9G2L+2xcJnOJ+Dlepsf%2kQW z(8W|dKBtm{24Am4hVX8~Y-H)&u%hcM_32ADWjr-6w#kRXVm^b<|8P zVu9990tx3g0T5&`1u9!dZ(<&7tC)RvLXs$8d8&OaW_L~$uOvjk-yRQ6UQ<7=vn76Y z{vxH|_}A15wO&qIL>MYl-;?;&MCoi@1dci;cH+znZns`)P83ffAIYPsArGJHppAor zES0t21kIo1$=3h8Amg<3@gTqW{bLNWUuPCHp3-yKM~~UXHKo-llH`?X2cFvIUF|UB zqgWddD8c@_L-770M_;ukY0j7-(gL*WmAKI&l(tqWdVED-=QTmO`-j*YM|@)erK|~8 zTl`;!x>sDOlO5uK+kWm$OFOX+@&~YfNJw+2YOBNO-nCY2U=JB^U;Kx7JIsTYc2|K; z>nr3Mq9Wc)ZxxWf5Y6N--XE%MP`CX2r_5tNVaLdt!Y7#ha zpyTkj(_s;Fse9kO8D#7K1T@aC2rEs4343kUkvD-qVi;wj6S6uh>E>JfaDq2D5r=% z%A4GgO2YC{#yVwUXC7P@*TX3Jg~QTGqtyBeh6`^D4y+Qqrj#+K-s{??!}~H?(Tj+e zU%ORWmJ7W7GcIE}wzwM?k3FQpyzH|Q(h9gssK4>{yyge|9W|YMGfAKNmH4xMcd&lT zM9uZ9>OIc83qgij2f9~Tk948ws)qzbSyDf^D6hN|x`d^AfQB2@D_1Y1F#;UD`Nqqr zcob1*%cCs&E?ecVL-BX#y;5CiS>`ndgPcEA!#@X(!S5a3)7eba^Deegrt!+&5k3re za0NCgqM*=&KX*d!I&u57d*PkOp2v}R?~pzSiT2P!hdQ(2$eyr&SMy>lfV6pHkZ4H? zt80|tfdI6NNce~7$}d5R?(lwJ`w1^$n^gMF?I`CUn%GkW!|m%*)7HtxSBg$pp9>BK z$sfoc4>T}h>tMuIqjZCJyhMDh9uC+sEtIEDs_jQ3F+YV-DQyR3cMK|0vS|*UR2AIHF(G&MV=BUcX?6Xy1$!I_xPey3RvcK)NeqHqAKd)JZTPWVymlPO-BMHJtwbd=3lwy?<<UYeiA>;}?&ENXua%2765}(deT`P~kZtT5Rkpq404yS4;_c{g<&9h$%fo^O zD=x?GuL$AnVUWenOHBn#N7r!4-8NNX6la`ROh=69pLCWy#BbKUvHuH(wHnLQ_!(Ag zQ~u1tx)k>FO4tR>@;7n{r17Zxj>Shdo0u$;X}}|A?P%8M4X`fk*C6Hjm&W30xvfO> z&OHKJ`aGUxaq)J}J?90d_vIbK%5b5(mTczVVcX2?5&2VE(%FFoxXU_b%$`+W-EJzt zt0BPmhLyOc@R%rwV7bTq6f5r}ryWb|Y$4tcvPj}NpRx6pu|jk~w2cu73C@}(vWrIO0yupQ2GA!)DJktLwnLT9!JLJGrxkn?j8p_y)R_@5kzp?#I zsCCfqVuXcoJLMWH6jr=)lKG<>iFmvXfAkq2cxYMYe=L4%A{62p5*B&?$Y_GeBsEPb zyO46X<@g*ibrrsVL`o|5@jyYEU#KGi;nyHYxyP9qqp5{8u~?Rg^V{l00TbhQt(RY~ zs2g2rJ*aWCV-$C;b;51{vFkZrz9-0Qm0KGMzr`6u?+rcUuZ(JML5&+CBHRyVmNDK2 z#5ucIF4jeHPaDL88_CJ((|pGox=)lHW==*+rG9xMeTxq=-wlUga6|_yqk+? z648o22^^Ma&nb>S&Fnhj?a61J*bHs$0!`BFq%}Rg;@Lh5+}J$qR8+*ZVSb@H4Q5t2 zxT+Ek6nt1X+iGF;pR457+qdMU)ggNKrSoyiPeV9R%3}ODZ^x8UyK=5A&+SNuj3!nF z6hVz5A0`A%H3%yx4u8++(W&D%+B(K{GaOA?IMT5=hKddoz@~gAr=ytlzGJvHKg|gJ zJKrFEpB?i6&$xzG7JMDANb=wlcX~e(aye&;tWE3rNX_>MEDqst+<`z8i61e7KfI|ow3gkYbF&f0}izUaz zY#Tid8Eb4bjY5|ba?%*6A_2tY0Gozflfl1+O@M8TYKL5tydr%J@G=woCt4Nj=#qo9 zU|(9|f60>k+A6E*pho?3F z)Gjy@xE~m$lv&xIxyOd#qHnwyoRB3JBX=~iPWWxioE?r;tj=?k`pbYDz^xk>!tS`n z4@v#ApJO=-V^TSLx>-j9bRwTaX(W6Vx%E}}KA(<(1OiwiAC4Wx0|0J4tC%q%BtD+u zWhsdwYwR@(xTmDKYkeR8WGo|jI#{#ZnPJB3X8=A*X3Gp7BEoMu(( zfh{_PBUZjI;ik|)fgIQ8^%naD_*8in+`tAdS{mw|5z?2AFLdn`NSZ+bhs7}8O?*?E znI;VpQ`3b{8S0x1{TLs%aj~7Z>aBbbF6z3>2+QjbS@tjbA}h<9K~wkcwspkB|H*G! zj81>UC#j&Z4gCkp@*s0?B4}EE#|VNy{V9F;qTp3HRa@XGCg6CjgSu3wh&(yVVf~W$ zQClzqr-b_qWR2BkYC7wnv|djZ%acBJ5(MQzwxI49a51tNYnD{*q}32YtysT`cExqS zzdi#Y70fc-M>@&-+2-xwPr}Ct;jkKiY%9-b3nGm}hln1AP)s&`K0Bv-Bk@ZEjFCwC zpe@3)b!)lPt_ZRKx0LUPMK@33;?|*Yx}zG{~xDn|{WgruIsZsOSh`wq7*}ayjTn0kc!Rc&0N4;D3fKDYV5MEGzG zt`UpXmY%#v(3?7D{jA-HI{udBQP+l(E7;bS&wu-86gy(cq4tEOxljZ>kud;nhGl8l zGx<#=9!ZfA95-}^(`%X-klPHCA^DrPC00HZ)LcLE>+s-K0q03d>!5ZIO>K}*($blrf};>lF9*KCNC_DKc2WEKvmn)STZwhumc7ZniEP<88l qKo6xm_s3Y<6t&k;|6B6JSEfQ>FE8Q}`0t}1pr)j)SSfEC^Zx*jv!e0< literal 0 HcmV?d00001 diff --git a/misson_practice/chapter07/images/practice-10-login-required.png b/misson_practice/chapter07/images/practice-10-login-required.png new file mode 100644 index 0000000000000000000000000000000000000000..fa0897d27ef019c6e67042ec09f3dc3c14cfa59e GIT binary patch literal 9115 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 literal 0 HcmV?d00001 diff --git a/misson_practice/chapter07/images/practice-12-mypage-success.png b/misson_practice/chapter07/images/practice-12-mypage-success.png new file mode 100644 index 0000000000000000000000000000000000000000..283481b0ae2986eb81f16feee09440eec7421144 GIT binary patch literal 10070 zcmd6NRZv_}*Jb04YjA4_3GUXoLxP9k?ixrMcbDLhU?D(or*W5Vv~fa^;O-8=ZTS9~ zhyQ7+{->FTd+$DVPn~^g-&6apwf6q1p{9t7MTrFf0C3+a$$bC-kiai?BM|-Np7;4q z>E(gs`aw|!P&G!q_cB1Wl~$Do0BRDjAI#BS#+aX#^j!e}yq^DDNCPeqhhT{p z2KPIxQW`()g276g)b%rHAKEZjAaH5c*Gj>ZY^N7w_b_?Z*LvZoF0kNN{PYc992>rf zc1gaTYs)tlzX5$|%pt?+t>e3IryCuKCn2_s4JMGm*#}F663E1cg;0ZfCzuh0GB|Pn z1M>(I4U9?H1Tdd<*zaUN3m1HM*%(UfmvPA^F#<{}FF=k41d)V9A7QnHD4o3$BI)XI zmOH+%7R+{QF?0_I*UI$$9>%}%vdFLIMA>7wLo(?tQD)>yqfxXJm6A+>auEH;55K5K zq7z>&jEW}?!(Hl57rtp2Z4Wzz$iH(BaK#t8X2!JdC&q>xTyclq^wGECe>cGX#7QTU zrj>|uBlhZ~kN|wpQ*N=RCu)Lkv1@=H{+6&B$dfK2t=6s4hWSS8V$d_xbk^V%x9be% zpB#}1S0CUY^nGRcVI|Mc`-AeomV^@*-to|>`gI|eUT9yY3PnZWFxD_QmWJ5Odr$l4 z%>em_mGAKau9iT!C5>H!FyWm`Fth>v-3@=0zE&yMAoP1;<=1Kc@$P$g(Dt`$pQFnt zvh5T!w@v-LS%U`W%FSl(0bS1WCp+e3m?SqP(c6QCO(NFowIc6rK=ajVLHK%M3 zcMZuY-q4-fdFzKOInvJIG;_+WnM5Dmgo6D=mQ=#?`f2+?apM+spNU6(OOCV=`?q&4 z5^eso8Cq(nUF)t5B-~f6*s|9*elk(1MWIGJ_dLD*E#Kd-j!1NFig3h(P6e-1ouPC! z3%pkP{eG-CY1%E-LBG537|KU{bQUq;*EJ&PhWOz;EGhBs&WreM4)*BqKb8kxUqLYU zMf@%W$dOJOf7f9t>xSfUPj#-YuoVT@rRhP>k+R1gHgm!2bTBOy4RjY1j4%EG7H$LX`Y?@`v^wJ_DV#hjC$Qk`d2U&Kt4DzBA~(wgk@! zwW>A>tKEQJQh8Fq15+%5&{}~s-&8EPC1Tjog^Q^6mD4DFQ@YGUDsKAJ{YYG%u)>G7^uEP@iQ7y^=?@rXvTDNL1KHAP)S`xBGh*+=Dh-itF^pzV zyhBk~K+_T{#dm^t_U?#Id0k7lLf8!*2+Ocx_m@}9Tlw-rgv?&K_XiK&A{&4{BnD- z7^Y5*aM{gePS!cImEj%nwq6fyL)FnYC2iYOM>@#bj6eFwiEvdvfvg;T9HgsQ!iUI~ z}2l$K&I^Z^j^IM+&5nQV|c7b4?R_0h)y<7hR57u$hs9xwKi#A2!DdQ!LiEZJ4hbJbyV&7V0n7vJ)(Pifi=$H0?UlSbK$c zxiAI&kpA0V;2j@}d(#r!pS)r^RP$esPC#rFFc2K8CLjM#oo>uOA3?uKQ6msaZS=YH zP3Jau5t#F1@87`wkKjhe<2dirobOA{&DtE}uk(cJ`?qSzADddm4*LVu z=>}?0(LzX}h$pfV5B-Y}vFn?L&8DIQkiXi2{BD0^^V zsnOt#htJSlR8vFEyR{OfT#DXtuCx-%7yu5-Ww8z*%?+*O&QP&yD-jKu;XeDv-9F@{ zO)i@-5pi5c*b9{>5M%yzRNuek00(M9KEMJ zlp8>!!(~wy`nlqAYY0M^LB(TroAbeCPtNO&!6PHEy8F?d^xFyKEn;8(U$o+LCUck* zp?kKW!s@SRfq;fpou z$j4JU$8_$uD_y zEA57XvaHE?$=Etpe27m!HG_RepV3i!qv^Mwr}n3>TZR0}AL9iwY|a@wV@U&i4VI>B z=lDkOxD8K$nJ(KwY1{e?**aN_`&G%66>7@Pjyxv>Ya;u>J0%#|`J7QRm-WlS3zgM# zPStY`e$r20d3stGkUY2aOkH7(*=aZiCKSDk`Ai7i$7&&0n2qS7BDLWvHQ{8sEh{1^ z&U#rbH=3Ld4J;FttC`3cMox*=f(wHAzq^%8NBa#Y1BHpbQfEhGc$Nkl40irT*%LTj zAQkju9>O`(*b0w&*+KmIq68o6*7&<45&>exJKoMUSTYeH`_alMBcE5^@R9xuc3 zixutZ?dAS-&5XZ^qxfX{76Voba`f;6vdm1?8NCo;(8R0Yf)p;)#Ptg#fIP`@0YMk3 z;(7Vc{!up2BooZL#i7#LJ}lvq{^#1o!E|~4w#01pj8B)hyw%%vC4a4*jkN$&HTYMA ze*hB5QI`?%E~*&~*fvz^g40YKQg?pMi3G>&jF$n0|FV-V&&LSXLb}1nsy^IT*es`p zv`l~BK{nn0ZjssMM~=chQ;cwRM4f-df_iY2hPMZ4vS=HcQpI0t9aPHRhkvEw?Fvd% zeep53*7ZZ$Mlf{bkpDx|q>^n>QmgjhFuhwMR4b{cWJfzTS`(q{jlkyJEEFs9W%?;h zFGavQ_|^82>Zh%~k+s9Vtco zYeG$XaOqTv1f3tL-p<*El)7Aw5KfZlL{eY#)u4^E{^@rT{WM~OUe_Vmy-k~jZFJ2G z8zw4y&p3jXglm(M5nD6BvmKs+R>Vqi!QM`%(#}1e@=gCxoUASr4&biW%gFakSKXPu z9O3d!e{RmXg%%9oVs4|QU*GMQ@!KC>JfJ1z>bjFm^Y2gV&KH+iRx}@PrKSv_Fkr7} zTd^5mr;cHFai#c8f3d5CCSycAn(H*!mTgqHs_9-8_CM_#+b%D06PM3um6f;Tgz^7U zP{xC;lOty{|C~)rG)+|Is8w_ou9-;44rGyhj$z(}6iVN2O#=K)YU=g*isk;PxLe2> zAY0Z3K|Rb#uC-7)MTbPf$BuW|engPU|wuRNuU2GzT- z@5m=I0d4y_-#r0;r&;?a`^i%}-m7KbQXE$ZF6_WCsO zA;#BL>>#W1QsT{2ji6UEWGNXo=}jnoibE3S)SZxPJ_sGm8Q<@|4w$2!_lr5@ahASc zqj&fBkXIUUu4pv!3#IzggmwB84{ptuR@)eJnu9O%z#3PPSFl9Yk4PbmrS@co2rb}5 zEFUXAFFcYz{xEIS^doPlN6tYjZwtC!Cl9CV^l)!<#f$P(Amlnxu7y%rM)%>wp2yr2EZ7mS;>_uY$d7=OVz)T%%)Y#rldTPH*3xv2iBJpu}lh&^<^ciC2G>Y|%* z8fB8i>HZQR^*aOwfO%G~55SA^)yCGLiD&RhAD>E8WgLduLV*~yO19rlGgNk;aA3Ae zlpF5RXHw1;jLP_@0um_YYfa0@gD=H=Aur53-291t+63z zrPW4(lRrO}KEU))QE5>V(`hzokBf7OQFIl!A=ao*X}8u?gO$I3C^P)Pw5(1tYhWQU zURg#Zw~U5m$e$KEuVSNn2;rQm5#*)->=fzsX^%Au{#_Er8c_yamte17rNr40E#y3< zyd8h@Nbe~bZ`NWs^L&MgDme^T!GUvN5f}`^iyiB)$TQ~92OLdsj40(+8}RBmZIW7F zzL`#=SgB6X{Rf<$$d>Cu4nZ%@Li(*?OGvD=8g6$-{s*a`69dW`lgI0d>GvBEj@eQV zBp(f2^G&~x)xI1Pm7JZyCYuv$O#$ti=%~{rh;jfFIEoZDbUCX`A&Nc_xz<=({`JV> zvY82lJBbewm21Xt(fxDAO;qPv=)AoIInBVF3S7nFBZpvfcl^aV3&QTyLPEVW`ux|> z+&Jbvx)9eL~W(WQmBUI#Ci!`0Eg#0?BuH1m{UK5HON!|EY@|n;!$)M{8;6U{) zFyuxrKX14>CMmD?*3jOlp%VxfCaK*`xE`V=gF6{Oj>UAFjWM5TE@7?{v4?V`+o810 zrYxQUo*yxoJ`qLWiSXFb_C639`pdS_%bHjqR?cNb=FnGmM3$CIb5qRn)uiRhX=MNe zt0KpdjzVUx&1;gzKMcvTl0kuOsQu?a&olS;?DzhsF=(&fCM(&ns%PMid7=|iC^Py+ zl}}WE01TPLIX3!yo8=VbKu1uxyNDsQKk&7P!K$h(%>#RuYPxUh)i9BY zV%a?VT4yhm-C{HJn4;@Bi4`S9WX#UM+S$Y%f1ETf`%K?w?r!bW*Gz&pw!@fw%6D={ zO$o}c3cnZEj2$fRw-5O=Z08tXv!0hj*MG=W>tsC(~nI$I>*RYXrwo#Hu`J(Rsq%ffesjnyOUN1%Z*=P5~i>6Z)x^VvWm`@+I;hbLBvq1Th z0fp;Lqd>FVn)DMCsvV-7JG@CdHL{e2ahH_+5*e-hRbJ4!r@4^wnK@qgIobK>!l&w) z48dyqXtYhNplKWV{QQ_85Ai(i9unYNzWTUi{Jijbz-JX(jzuo2=@2-DF21GN~V zUw(%Z^Bt`LD6>Y_^Ph?YBqI-+Q{DM#H&bJn_YbA^3jk|Y(c{iCSa9Q|s6`BJ!XSFo zUrmkcd1Gpe2{0C6@LvI?I72bo|hzPWRKxAa%{W9F&E!zgk{%_LnlKrmAs3g)= zB&L{i8N*j!uvo*PWRL|YW~xDWU5F21@29kDESl|BgIjc!i`$%f;Egx?h2x-vZWD_S zNK+5{*N#RSgP9r%8X>unac58EiclC50|YT_Vtg3g&ADI1So0=k=Q1$!F$p@kpf)X- zmm}!rl$OjY^G9J{`fBlS8{O#co=_MKNv5NKkiTGfY5Kl0sr8mVn4y;LTB=%af)z** zrlZGB16(e1J!@V~P6jBkWYPZ#^Od#ZYcSuX?UEYk;ZG)h$N9n;J&*@Q(j;C`)nGx& z3{lijQN+ZvN0-N)R|MBm`WEsg2G}a|@w4HW#S{l@avo`=6oBjk2DRITTpZ%;8yVxG zji^`$i?S3r=%f;Saa^*+ZKjiM>K8BfUvX|Hm5^2Ko&dcaTf=#?MeuW*Jp#`i=3VmY zq7J3{qVJOZeR@We*}rf0dI@(E<|&D+&!VTv81Y$tXbtP50a44ZjdI37cM&wL>?L101HPMH$fLiG-74mQvj zCF-!m{dzDeODq_N%nK{$`qz;aY48DWcjPB-F0!PSMclMuv^8f+6yNDBT`?6#aITk~ zyyOEjJy;VH-y#keC}9gRf_g^k3cqN z3PbI)8(e5B_*n_-F>(wJc1O=syIhySn>3JEO^r#U73O)Z_^X)#0J!j=N~^=v<0u92 z^^E3u%bQ3HEppdMIS8=*#*SDH%S%i}y^yh5YkTNw>4N%FtXh{lew#bZfgc7cLpQnm z_3m6eA|&zoT9UsAjmS-lc>fo)70H1%j3)ml*FJG)?wkwt%r^=AM+5D}9{RycwTE2y zWv2qdIeMZ7-P|>48}Lr0#jgwA2-ubkh`=r0o=dj)^5?$X*T4QMhv&*JUNwF|PbCFi z&vx1$#YS?|_Gu$PFr9q~_s2l5spZoNB~gc&Fr{@ZyJK;AcEqf9k~&&Cjm|br3~$$~ zkt>_nExL=s`tRejx8MMzm=2@}A>C`43o-6~azl&rL*PZ1lTCH&U!_!@E3f!C>RR6V zsF7#;AA>_Nqv=oX>-NSwFS&U}Tv8x=+`VkhcvPiVL+mhgpbSy(vnpyN=fseYSd@!l zdHw7dI=i7^Eh=X6_bF4Z?6-RA)|)oeSDrGT9r)`~N}tl-+aHEQi&e(z^gAa-*mF-g zCg#i9KLgA{ADMpXAzW{N_PxIIY{=Vi!m^2^a#keNJfYPSiA>bSV|h6{-W>5a4LJM% z=yk^eD?ykkMnrGp(Fir&b+PS4k3*ZYxY~6rPmhS`KlX>Zr+fZ2%cKa}2+IF{h>QyF zyt~HuQ+-78>kr@q%Qi2$e;aOP74xBP5utxiOwxj;u*=micU_?f=PmvLHKFK#XXSSO z%gDKWsHJSt-rht{{s05Ho38IXlG_n(jVK{~VLG~(r-OARHi_TpHUZ@+ZC*Bdl~TvS z6+_0ELp>*K0t*eBR!VmA(-#h(gR#a(prX7y1d3S=$CWtH?pi2W3>J`5C zTlA_}Gr5#8*b5R8S>fOiy)dXVro(5bqp-5cKbyE-$ zwX8lL{;Cv_2nlj;tF59~zAzVdzM1&q1U!SjwsMi5SXmBsMU(74nQC!QiwRN<`bAwM z&?HlhtUQ$Z*MzS7Kghff-(55_t!G#9-CxfI_wm5v7->{BG=dx5_k`Sq(_}h1xF9{+ z-4h9VDPjGcS28t#y>iIv3k&jlU#@BqF>Mn%Y7?&S=w`6fmvZ5x(-jWA<{jRLPgH_8 z>39&O++w~hj!PEuFSQ0lLRw;1ru-)Jb7|*$G10i)ToRurE0#}M)v!qkoV9#@1;@An zfAQ$2KReuR5>dnt!^E6f!v1)&WFb_PPT8cD%5B}8(Xg;s>Y(Xy1ob9HkXOlA=p+FZ zwW9cESZw;TFLcR6m|l7aNdqr+=e5#jSf^o9v8^|HsIBd43omjzjofZg*A;fmJ)s*N zwcL+!XrXx6p4CxmI{z!hb{o@PtTO>!*QJ_1WGwEU<)N(;Dk&d$yqPI5SQbPwq7xOV z$n6O?^?bY%LSGeG!rw83AxdfML2+w;0&reED!H8%Qk@2d0v~6?KhGo-><+-lB;*oG z=q%29>_SIgN`_9S@x!MiudQr(6Gs?~Fy!>JOEhRWnqiSwej{U)nEtDl0 zg!5B@O(?0knDAZG;V)_iiT6vwmOv1(xqP7fPGiMD&CY&?O_}d|)R|?|GhnQ;Xm8n= zg}43W8#~H|SF=Wje~WrypleD!Y6{_o!Mo?)2cu)u3kzw1VGL)|AW z=`=RmUZZ`zp2Yja`R*6iJtIhLuEyzB7w2M`?W-0#)1i5;5dCb`=eWRb0=IoAK~tnQ=R5NYWQ6vz#e$j?*gRfHhl$ceAQj-;~cMF!nP%5IO)AHtC48P zyu{_jG_tKTqb2do!EiCKC9-3YjoW4O5}k%WurxpM#&bAt^4i6)Ta|YMli)Seb84FM ziKk#Q=kgt$)969kOKjWOi(HTR518$34F z$U#H>FjTA4ESZw%rKPajp?SF=6YsZy>Z8VVfLA7|x-E&%U~D_KO1BSPYygf+(S*KmY6IVmX)XsFKEwzf z{FWX=|KilDK!QTvA$Xk26_t<*UuFXVwy77K>+yv^5|5D)R@yGou39WsNiuhi@^4OD zO)tGHm@e%*(Cekdwj)oZ*ebYMCN9S48PXWYJ9sek$1K0{sL;;t*`!6%ViCW4{Z?nC zcL5Kb+lwXoA5%x0r)4_1W$d_#clTi%?i&n!;L_Xk>5&e$i|&YD`{f z1Iw%?MH`zEf^z|s`c_3ppEKe8^2D-@eMlAEMH5v{$>6ZxWCDUcCsXmRPPq17?3AV? zAmmW7N7$7)A@@ogxV)A&!@y7>_H=1PRe3R2F?3t`u3O`tHuh??d|-2*Q`R0*T}stF zMo>vPP})!d9+V*VadtE<*Ukb?`6Klhu1JtN-?#1SE!D=W$kOxT(t(|gnn zzUNNn)5H-2cJI-4$6AyPV!6g;pv0+>{dFUXm{&b`HsM$aG>&QYfAFCleJORk6TDV( z5&MN?V2KnrIk)}LwWPfvX)yTcQh8QT3Hm*ZZ|BGqcj58En9Cs3?|j)SDH;YgDJiOF zX$M&1&Q7h%KcHNHvfR-)A$QQ<5dP?q2p+)`Of;gQ$Z1=*5e!-zHy6~UkQ-C4J0+y5 zvyaPlJnRzDmi$ksT$}q{e+S|2Rgq$YNz9hg)q^aMT9C~e&b}oc=yB$qRs%2avcT#M z74DfcQMUC&!84^EXDrKceMf?SyOwkuSWG%8!%eg{Bh9Aatw`dB8FV z7ux?y4Cgv zzOG-JYeWj_{*i(p{BQ4)*uCppItO-VMJ|D$2rVH*q1qEbx+Djg*l(1Ma4{uPL&!gY z#MJ%vZS3Wixz7_Jd#aeq`gN3~UG=oi4U)JFVXjNIte1soDs59pjSlyfB0Gm3!4tMD z;ta)4i1tDN6NGMM5z8Y{2bS!%y~Au?j@~u5eo{vm8AcS0tdDZ$@8n@(USTxr@}?#E z9T2t_H-f$#Tz}S2BRn#cd9M7F{&a=E>x2}<5HX*|J#PzNytmn+)Vvw@wDXTKEjFm- zN;qD;Km@kX=mda3y&8f%2!Yl9Ywiv_cc~$cb?r4C5!>e|xb6_{=oa0V^}ZbXz&i#D zm-;CjNb?2IcRp?HW-7!EO0iA4)orTzu&sC_LdSSfg?2#)amRcKNK+fD@snD9OsR^b zc`US^mamK6+EH4a35Fy!e6l8r*Xtt4o!a;un3(N=G{y-(7i)s1o|xGO;`o^<+_9qx z%LHKWH*OZhpdH@2Q5Cs#07Sbq;7*S;6{`Jl>pLrO$UeAWi)@W~=N%dLUsrdkZI`Z+ z`4=%DNPJ^~Tk@Xaa4XfQ`O#Z0{-p>i*o>c{*>Kx8fZ$-ogKmNUdX)U|2qOq}WJlPe zX2tz_=|5>t@#92<8)S!`1Y!Q24uN+y(LW|ItYb&Nch6Y8J(X|%}z_c50P<%`tC}j2%$d6eQhB=;qD>(r&HA!Un z+B0VsVFc$R5CElay^`wJ9n{&F?NBi5}NXuW?$W{TX;HZ;l{31yJ!!aG`Z(w)&M9AJ( zfp|<#PWao0AlNASTVi4v9QiJ$w0pvxF$b0H>4U1ge^-#zl?Bl94Q1mf_5~$ z&+8I8+8Q@Lt@#%5ijL8@JZjZ&Sjl-MVo)DDx|sC$V+Q8(MVIXE4K@MQGAU?NzvxB9 zW(1PSht;1V<}1lPra z!|}b})xBI@)%5gqRrlM!rheTWr=_6;#-_vu003YWWqF-v-|}qnAdKgmB0S9U*`avr zD9Hlq#;Nz719W>Cbr}GlAqD5&68Icrc_F=4ugBZM~yan1IU5RqSuo5&eC1)tua~M*l}c2 zo0cy3wX2_uag^yakl9p=a6NMn(^RJ<%cex_o0cP-l;w-n*Y9qMd4VDL(~W%gEa_4Y z)&(a4J(y|d_S?U5Ci2fkiuk;f!y&TW7P}ufeE^Xqtl1HKK44vs=sHX6@ks$_4@Fxv zo3Pw(9-#d$#1^4aQ3r}C5eL05Ssy6aEtQ46@P?RBlL0iOv-ngBnsR`9 zvgQ7z(rR8tuT#x+Jco$7N1xX*A>jDE0(ndbunf8FYxVX!=`fP@9~FD@x#dxyy{BG9 ze~l1obzY;`+A%d$UB_Dw=RVn7&|j2|12@(>K{C&#R^_1P6!bx0nprVXJ;b48bdj() zy`6xVvt>iYM8FrBh=KT{9^+2aFHhWga?5i;7p9ELV_e|zU3gv84rm}#F;1W~@rXZp zx~vLXlSmrDQD{WuphrJC_MPVOihg2(a!Sg(i&%^r4-yWS^Zaus%jL$SPc320szj`} z2!?fjyF^r0A{^0jLq4E4qDzN-QA5=uKMuRmrG3%HPTcXb_B5W=bFP=@gcwFDGPByQ z+Bp~Zmj*^kLW;iOJ|pa>AhAcSHr<-}vKd&!(h;gF@;lAr2eiMOK{2Bd0K)ShDOdby zM2wT(|5=;GsAVu@K!hYv7m5=yQ6l`$DXJ6m$qzFphdgk9Y^m144maz7)8mi%*M#4I z(+a%&04Dsulwt(g$_(nDG(J>x5*4BdpI2eyj4T1$Pw%=fVX`#(gbWcy0>zQ+>7FUz zWI-|$SGbc#q( zt403tf)by4f_fYpuA~c)mw$I}Y`3~LXoi>0n=D4v{0*D67v$-^RTx0M?EOl)RduQ} z@)Rdbr0f#EcbOSa4^LT6T8K{vGXbgi2cvx2#HKfEfpOBT(@g5YN>$d>$K0pngbsZ4 z^^P({zm-?YI2z&)MuYO3%8?Qa)GVqbnCT;p*z2lJ9TC5|8M(m1D+$V9UVL9&{CmoJ zOVGOW9$el}`-6vv1M6jaTgt%}O3HZg%>jh4RQzoQ6x-S$57Ehu|DDOI4dT>xa%H$Vti1>FvaGM`zo2}k@9IOi07`Jnh1E#3#aL4 zveT>rA&bN!Wkw+Kp7=ILH*Nm>@jTl4>S4l_=i36>gJN+ix%0Cs3_iKca4Nx@ru;KC zD;&R+0F~tT|2ljmGU$U=n2?w-Bw>zjuHSrUl#0$iv!QYtva9CIn`+fYK26cGl-h;$ zQS8w+?w4|MmYn5yo!M?sv6hE|i~(n!TF*@6yMP-TRW%tY+`D$0kGJ|I2slJ`+D3Gj zIXNYf-S-pcpoHLbhiu1&nmePK0je(u&JYERXs&+DKd>`&JRx0unH6h zJoy!PG_@j}yn8~r(PvQZ&Ys3k?vXJpsrih`nn~S3y*kq^ayXB&AV&MDz~S=u+3Iw> zpZOE^yRiGZT&i8YZ1(H^F}V#p_%}^*`ahaQ3ij->n&H7rFb%7~wm5YV!-TFImE=H` zbFPeow}gL{@TSG8B~L$2QmMu}_k6tkA-G!x3?-a#G5pCQ*KBJ0dIiNhDL}4#FDL;2 z4faaUyK8&U^pwUhj7?m$%(1*ovIU<#rl`6R{fiB1;g+mzme%rj^sY`fT-I+f)bWay98seUd1{ zeM`SYJZd`P({PVK*K#T=nJm22M0~a2s^H_8Rkx2*=)sP zOSSYCwUQp5TLqTF9^C#-XprYJn;|<)vlFLQ4+&F(wC*fpHa`hN-XeSIUcaH~a8O)r zCcJ!70&MD!HycOL-v35v3~4?)i(sh&1^bny(Tt!P=hg1EeWO0m>8vk}|9Zc*E**9a zFmjJSja>8VMf&X>4j4&~{g_k?DCvwT4k1t(#YyZ;fUnaF*`<`%b_Fd0_)n~P86J8-M_t)vWoJTHxqyJPwnNosPNOhYj8 z+c#Ez6txa+RWsk!opYxyGfck)nSfuS|0oceiu;vsGU5^vB#A=t?%YJ8K7}zUE0}$$qDb_N!7%C(Ac0x8}=aBR(|w@_DbptbK=m}rG{Q!8c!%9snvaGiRCxmApxAmpFGDAI21=jJtd$H znFysR*M-}c(@_vq$)m>Es>uAhvYl8)uRV>@61R142Xbfe=d_o;R#3;;p+lK#P4ac> z+53&g&r&;2tnl;gQh<;%PTiLtoRVwd5)=H9>NO?~6g9M5{K^!7PS-_uKq;CGi8=k+ zH2xEq%%6k|8MuhB-l?8KcAXhjb#c)J>v!BcNB92YKH}6GXl}O+ep5fmDIjL1t#QN~ z|I9YTQ8OEk5rKY-pqV-b`{r_5Iu7i%7pB!LPl^46n5H~IECK% zCpub!bfNR2TL$)EMu<&h3)bQTtL4m@GG|(wehktk7a8TzR$PVa?x|*dj4;Th$5A0X z{;pd{VzjSPzeL9Ov6x4DUV46j{}tG-)P)ICV)vHnMYd^V`GY?C$OY5w9zF%T@T@sV zS{WyH#mS&X8xyTQ@c- z1`dVAgM@|rg;V!}@@qUon6xO0Zg# z5Mt~JlXTB-G+*6x2PJ$bsl@Kj4SSXB|M?}?0VeE08u3-+y?wmp7VwXMFb=q!gy44? z^jAg*OTF18*4%(^yUReS9H`qJBuiyQ4RG+ z4lVrO+v6Oa1W}G@QTp8$wGY3yiIiGf1ExU|sE6!9fUVfjHF)&hyCknIMtCwMtKNwh zg&3tf(@&Q8t#bgJLPjpwWBA-|ymQTJ<+_o#%IW2F5E)1; z>@J+A*^rEl@oW=QhfqlUYn=sbjevB_8vOzC~@>$s=nqQw46 ziO7m}cueH5P$v#V=~z^6qvA(E1k9||u#Js-y($o=NPzH}mvjkaK{@TcM2FuA4DvWQ zl1Nb%a=w;Zqf7_Knzk|lq??mmq31sPRKUiQW9Pgqqwkt{vEbd9*ubL!=6X8 zyMQg0Em5Yd`t-s6Bo)T!ai_*ICeIFn)3y}`5Zd`_sBgqwT4|0Lw~|{oBKdd>D78jt zmA;HdB57xKC4QkGEPhBPZB}BgJp8SMt%Z%|@`0vVZ=?p(B8=B_Aao#9bo#gpR1|uS zG*WcX)v{#-N#^SGA-&JrUp&9Fwb=rA*fsG`hg@FHy;fT}R+!w4NM9o4rT|;*J_Ez! z=P8)I6}j+UuLX=dqOZe4{8)N*k3K4A^P1k7aB}$}j?XhTmQfO?zkZ&}RE-G3wrW25 zHl54k>IWi#iDGsFulxK2-DOAIBi}*sJkE}vR!4GoP!V7DuSz-DbnHDs@j)X%+;0;6 zjCilJ9x5A16lI*8$et4lnYt{#6ih@dRb2XAnzWCwdgj~FW*NNQ+; zW+HUUgY+i%lnHwejf4jD^ntDnV}j*oWXq!`AGgT&e@IkTloCq$^}zmsuMkWm>CBgE z__K;q+wC{1=^Av@U%^+NLvMKU1)Utfv|vVdSWNSZ}$YToNlNm!2^n)My%)}*L{*Aq} ze(KrC4c>*xxeKL2kglGEWb|YtIkpJMS}F=Ko;bWp-!DS*PmxBEyhlhda)NoZe{yCa zd|^EVj`#E2Ib9Z+j}sZx#&gqphH<_I7ckWv@sxs>g+R^SqnWStj=Z{Qo9TV{;QMGm znkFcyGtaK2>r3g05x1575B)k7w4YHb?{OB_dS)>A6*{sD9VVn&K6Ez&>GDV2b}5NC zzs#S1AztEd8jZMB4AQnWj{f%HFvAc`8bkI#dF52L7;z&!7##_k_WFi4r1`GceJ^xn z6Mg{s>PS0~fiKSzRia5XdAgA5s<3w*Y@MApnLh66=F2Z0>0^9P2A#oTS=e5I= zYaw-Dk8ah!x_h#KdH2ZktwJa1JUo=l$myX30X#lLs?Xe1`-(L1kCQ*a02D@Qd&Pu zI53a7a&3s(GT!SHkfWf66*m-ts(0`pn9zRDRn2?9;QqUTw5*CTjhY`&_+4HW^z(Mm zd5u@KWYofu{%kJQB=28YX;2xAyFjs^e@xS&QY1NSAi*OTy^i60tAbP`O3G|6kF0$A zx}F9ZN{b%K8CHk3NcT52q#wgIHbqeYw%6e9jW$)o{{TV=;qU*l%MReEp`{(#DzbjL zIYlKU2IRKj7a&cjq<7t_h?J;6wWmLc=OQXp5H(3H z$55it&(M-HA8u$G(m^Lz8JH1ln|>K7c03}N9-&XcZ-i{!)KM#7+5-k~Q3M$2%7Wv56L-f9e6CxCUpCAo0MrRa79Bp zVANo;B;=nIx=B~KeJGkZJ`r}22zzzbcs+yI=e=3?#G0Q?q!KB6*-4fe*mZu-+7$We z-&zClyeRNu2wuRqoCT|8ym%mzeCrk((Kcb_5hd%e=8PUnpSag!gu7UP9Mg#z-YMiK zx48ZyHzyxJWMf)4N;W1KztxOI63fP9Xj*6C5;g*Jru5<6~|;e|=Ix{keV))Qw1vR6uTd&*~5V9EYn_dkEUCR(mu zfdNPE0fDYCxI>tueb2{yLHPAmXsM+W72Mf)@|#pr_)JE2d-2y`Jr*=x-IBJidPpBa zb$_zWn5*5Fu4i*OEn)JiKlV`ogS9-K#(P@6qc1}*w!JTsen#l=6yM}sSsIuJzSI2% z1oW>x^-ivmeJKBCY;FVE_@>46SSfRVN5(^|v9s1|ozsQjXR>_BbhpEwl5x##`j45y zY-!iAkglYu!;Bp~QNo0~ysc%JBfnK5l1O5N^Ee`&>y*Z`Lv+_}m&2PMy?9^xB|k`I zM^8uNCJUlS#tY%={?qbC_f?*Q$akFuR6x}=Lqs28>kamhCKufVX$3HD0E8qSHKjX4 zv>1S8`E52}W0w_Cy9~hwX6x5k6zDO`U9=rsrML`%lnN%K96St%(xQ%VrC-G*+IJ{ZnBi4B@S&nb--e1pA{vz=-wHA_Nb@NLN@42wse)+c z2GA3cV>Nc{m3^#1*x0UhpZTOobkb=LKgq1Iqq$8dPi2tYd%Zp)Vl~9Kfmi!MYVr*F z4l2SvG^PzL*pj)+SF8DYf75#zF-mtX&|6lIa-ct^uUh~_mx9Gf>H^#C!+#caN!P^o z%pn{zxXc*T*T|;Svs8Ti2{xu`9WuFy#wpgtv9Dtiq})d$hhgN3qE}Wad8wMb)(XyPk-)bD1bR$EC7~>`*+6E16R{x;>?>1Vbe$aulGC zc>VVA(3F*hLBMa*t;=mD!EY~mFa7rNU+Wu(EMFc$?DWS)X04=4t5M~?er?D6%_X|C z94^~K)J!(XiaLUc<53g}yvk5Gf{_jY`)PiaRBZ!uk0~d`P9>MF+)&sP9cTr6??N!Z z+dCd+4>%3t$0CeZc+gMxy>Hm!4T-n=4D%Me6O#?CpH+n}-QF6e6~`HHsj+oATK*Pa zHRt7iC#5v7(#wUz%JqCK&cr*UDDvUk?=SfJGTBw*qYhDnor(1X4|X(7V+jbZP8|A2 zDUXx%6-XW7(bHo1UJs|TNod?spl>>}Wc3D$_WACISkIFn2g!yP?>>ho8x8hSg)RP` zJvEof2KskqV;(SLaB=qD&k6eEl1`m=eZ0;CG2rhye^j_z`pNb5u%qgpK_7?T$<*ym z5nN1YFZ@6T`=CCd3KoZ<*uyYRGyT@Z-qfgk0nXR^(G_eX#O`RI?2iT(9Isg4S=fA{ zWNIr|!t};*aW&S?D{U8M*Kuh6>b0KY7PV!GA-PBM`dd8@65=3P9VkO+#`sR)xlcEq z^zFnhJo(NLVg}!tLM_lznbQ|u{$A?E;Fm<z!+@wDT+E zXd55Be3@bVNS(k$QNUQBl-}QO+hN|#7ZGpY-zcQ8=V%Rzn}49z9s8}n5*A*L4EDb82*)`-WQ5TcAFGT z`$ijp%m{h;I3sWqMgY)ow_lNbwBM!;`W6rwsJj}a7TL};S(&}V@lW6@YUtACFcSIhank|o3w5G zy^PB>vlVmGF$PjeUnnoT{@!{g-Jzue_+Y)fzNF^6cFusIQ89Jv!O=VNqB3i!^TniK z0_r+&&nb_QKqp?JwG}eB6Blao%Q})wDTL}fAg_G W=leOQr|0z{Kt(}AzE0L6;=ceMNw&NI literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index a0b2bb8..15dfb73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,29 +1,70 @@ { - "name": "umc_10", + "name": "10th_node", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "umc_10", + "name": "10th_node", "version": "1.0.0", "license": "ISC", "dependencies": { + "@prisma/adapter-mariadb": "^7.8.0", + "@prisma/client": "^7.8.0", + "cookie-parser": "^1.4.7", + "cors": "^2.8.6", + "dotenv": "^17.4.2", "express": "^5.2.1", + "morgan": "^1.10.1", + "tsoa": "^7.0.0-alpha.0" + }, + "devDependencies": { + "@types/cookie-parser": "^1.4.10", + "@types/cors": "^2.8.19", + "@types/morgan": "^1.9.10", "nodemon": "^3.1.14", + "prisma": "^7.8.0", "tsx": "^4.21.0" + } + }, + "node_modules/@electric-sql/pglite": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", + "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@electric-sql/pglite-socket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.1.tgz", + "integrity": "sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "pglite-server": "dist/scripts/server.js" }, - "devDependencies": { - "@types/express": "^5.0.6" + "peerDependencies": { + "@electric-sql/pglite": "0.4.1" + } + }, + "node_modules/@electric-sql/pglite-tools": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.1.tgz", + "integrity": "sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA==", + "devOptional": true, + "license": "Apache-2.0", + "peerDependencies": { + "@electric-sql/pglite": "0.4.1" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", - "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -34,12 +75,13 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", - "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -50,12 +92,13 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", - "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -66,12 +109,13 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", - "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -82,12 +126,13 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", - "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -98,12 +143,13 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", - "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -114,12 +160,13 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", - "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -130,12 +177,13 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", - "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -146,12 +194,13 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", - "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -162,12 +211,13 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", - "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -178,12 +228,13 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", - "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -194,12 +245,13 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", - "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -210,12 +262,13 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", - "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -226,12 +279,13 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", - "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -242,12 +296,13 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", - "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -258,12 +313,13 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", - "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -274,12 +330,13 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", - "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -290,12 +347,13 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", - "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -306,12 +364,13 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", - "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -322,12 +381,13 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", - "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -338,12 +398,13 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", - "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -354,12 +415,13 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", - "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -370,12 +432,13 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", - "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -386,12 +449,13 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", - "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -402,12 +466,13 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", - "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -418,12 +483,13 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", - "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -433,265 +499,1738 @@ "node": ">=18" } }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", + "node_modules/@hapi/accept": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-6.0.3.tgz", + "integrity": "sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==", + "license": "BSD-3-Clause", "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" } }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", + "node_modules/@hapi/ammo": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-6.0.1.tgz", + "integrity": "sha512-pmL+nPod4g58kXrMcsGLp05O2jF4P2Q3GiL8qYV7nKYEh3cGf+rV4P5Jyi2Uq0agGhVU63GtaSAfBEZOlrJn9w==", + "license": "BSD-3-Clause", "dependencies": { - "@types/node": "*" + "@hapi/hoek": "^11.0.2" } }, - "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", - "dev": true, - "license": "MIT", + "node_modules/@hapi/b64": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-6.0.1.tgz", + "integrity": "sha512-ZvjX4JQReUmBheeCq+S9YavcnMMHWqx3S0jHNXWIM1kQDxB9cyfSycpVvjfrKcIS8Mh5N3hmu/YKo4Iag9g2Kw==", + "license": "BSD-3-Clause", "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" + "@hapi/hoek": "^11.0.2" } }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", - "dev": true, - "license": "MIT", + "node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "license": "BSD-3-Clause", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "@hapi/hoek": "^11.0.2" } }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", - "dev": true, - "license": "MIT", + "node_modules/@hapi/bounce": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-3.0.2.tgz", + "integrity": "sha512-d0XmlTi3H9HFDHhQLjg4F4auL1EY3Wqj7j7/hGDhFFe6xAbnm3qiGrXeT93zZnPH8gH+SKAFYiRzu26xkXcH3g==", + "license": "BSD-3-Clause", "dependencies": { - "undici-types": "~7.18.0" + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" } }, - "node_modules/@types/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", - "dev": true, - "license": "MIT" + "node_modules/@hapi/bourne": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", + "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/call": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@hapi/call/-/call-9.0.1.tgz", + "integrity": "sha512-uPojQRqEL1GRZR4xXPqcLMujQGaEpyVPRyBlD8Pp5rqgIwLhtveF9PkixiKru2THXvuN8mUrLeet5fqxKAAMGg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" + "node_modules/@hapi/catbox": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-12.1.1.tgz", + "integrity": "sha512-hDqYB1J+R0HtZg4iPH3LEnldoaBsar6bYp0EonBmNQ9t5CO+1CqgCul2ZtFveW1ReA5SQuze9GPSU7/aecERhw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/podium": "^5.0.0", + "@hapi/validate": "^2.0.1" + } }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, - "license": "MIT", + "node_modules/@hapi/catbox-memory": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-6.0.2.tgz", + "integrity": "sha512-H1l4ugoFW/ZRkqeFrIo8p1rWN0PA4MDTfu4JmcoNDvnY975o29mqoZblqFTotxNHlEkMPpIiIBJTV+Mbi+aF0g==", + "license": "BSD-3-Clause", "dependencies": { - "@types/node": "*" + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" } }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", - "dev": true, - "license": "MIT", + "node_modules/@hapi/content": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/content/-/content-6.0.2.tgz", + "integrity": "sha512-OKyCOTjNR1hftwSjk9ueyAQTw8AwapvzBrPIWMGn39vhR5PmqLdYFmLc35bsSBye7gSMnlkXfc679bUdMIcRyQ==", + "license": "BSD-3-Clause", "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" + "@hapi/boom": "^10.0.0" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", + "node_modules/@hapi/cryptiles": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-6.0.3.tgz", + "integrity": "sha512-r6VKalpbMHz4ci3gFjFysBmhwCg70RpYZy6OkjEpdXzAYnYFX5XsW7n4YMJvuIYpnMwLxGUjK/cBhA7X3JDvXw==", + "license": "BSD-3-Clause", "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" + "@hapi/boom": "^10.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">=14.0.0" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", + "node_modules/@hapi/file": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/file/-/file-3.0.0.tgz", + "integrity": "sha512-w+lKW+yRrLhJu620jT3y+5g2mHqnKfepreykvdOcl9/6up8GrQQn+l3FRTsjHTKbkbfQFkuksHpdv2EcpKcJ4Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/hapi": { + "version": "21.4.9", + "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-21.4.9.tgz", + "integrity": "sha512-YnecZOVx2AD08VvPl0ZaFS0MjEHqg+InGRmBRli731ct+VwI++dpu3BIYA1Z4SMr6HUAnpyvbQ1aq5woe3fBWg==", + "license": "BSD-3-Clause", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@hapi/accept": "^6.0.3", + "@hapi/ammo": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/bounce": "^3.0.2", + "@hapi/call": "^9.0.1", + "@hapi/catbox": "^12.1.1", + "@hapi/catbox-memory": "^6.0.2", + "@hapi/heavy": "^8.0.1", + "@hapi/hoek": "^11.0.7", + "@hapi/mimos": "^7.0.1", + "@hapi/podium": "^5.0.2", + "@hapi/shot": "^6.0.2", + "@hapi/somever": "^4.1.1", + "@hapi/statehood": "^8.2.1", + "@hapi/subtext": "^8.1.3", + "@hapi/teamwork": "^6.0.1", + "@hapi/topo": "^6.0.2", + "@hapi/validate": "^2.0.1" }, "engines": { - "node": ">= 8" + "node": ">=14.15.0" } }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" + "node_modules/@hapi/heavy": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-8.0.1.tgz", + "integrity": "sha512-gBD/NANosNCOp6RsYTsjo2vhr5eYA3BEuogk6cxY0QdhllkkTaJFYtTXv46xd6qhBVMbMMqcSdtqey+UQU3//w==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/validate": "^2.0.1" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/@hapi/hoek": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/iron": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-7.0.1.tgz", + "integrity": "sha512-tEZnrOujKpS6jLKliyWBl3A9PaE+ppuL/+gkbyPPDb/l2KSKQyH4lhMkVb+sBhwN+qaxxlig01JRqB8dk/mPxQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/b64": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/cryptiles": "^6.0.1", + "@hapi/hoek": "^11.0.2" } }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", + "node_modules/@hapi/mimos": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-7.0.1.tgz", + "integrity": "sha512-b79V+BrG0gJ9zcRx1VGcCI6r6GEzzZUgiGEJVoq5gwzuB2Ig9Cax8dUuBauQCFKvl2YWSWyOc8mZ8HDaJOtkew==", + "license": "BSD-3-Clause", "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "@hapi/hoek": "^11.0.2", + "mime-db": "^1.52.0" } }, - "node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "license": "MIT", + "node_modules/@hapi/nigel": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-5.0.1.tgz", + "integrity": "sha512-uv3dtYuB4IsNaha+tigWmN8mQw/O9Qzl5U26Gm4ZcJVtDdB1AVJOwX3X5wOX+A07qzpEZnOMBAm8jjSqGsU6Nw==", + "license": "BSD-3-Clause", "dependencies": { - "balanced-match": "^4.0.2" + "@hapi/hoek": "^11.0.2", + "@hapi/vise": "^5.0.1" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=14.0.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", + "node_modules/@hapi/pez": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-6.1.1.tgz", + "integrity": "sha512-yg2OS1tC0S1sHXvhUtWsfRn6lrKl9jKtRhZ+EI0woOW/gqX5vM2PZ1459ypCvCYDRLJ9nIyueeEH5MJV1ZDqIg==", + "license": "BSD-3-Clause", "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" + "@hapi/b64": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/content": "^6.0.1", + "@hapi/hoek": "^11.0.7", + "@hapi/nigel": "^5.0.1" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "node_modules/@hapi/podium": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-5.0.2.tgz", + "integrity": "sha512-T7gf2JYHQQfEfewTQFbsaXoZxSvuXO/QBIGljucUQ/lmPnTTNAepoIKOakWNVWvo2fMEDjycu77r8k6dhreqHA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/teamwork": "^6.0.0", + "@hapi/validate": "^2.0.1" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", + "node_modules/@hapi/shot": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-6.0.2.tgz", + "integrity": "sha512-WKK1ShfJTrL1oXC0skoIZQYzvLsyMDEF8lfcWuQBjpjCN29qivr9U36ld1z0nt6edvzv28etNMOqUF4klnHryw==", + "license": "BSD-3-Clause", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" + "@hapi/hoek": "^11.0.2", + "@hapi/validate": "^2.0.1" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", + "node_modules/@hapi/somever": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-4.1.1.tgz", + "integrity": "sha512-lt3QQiDDOVRatS0ionFDNrDIv4eXz58IibQaZQDOg4DqqdNme8oa0iPWcE0+hkq/KTeBCPtEOjDOBKBKwDumVg==", + "license": "BSD-3-Clause", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@hapi/bounce": "^3.0.1", + "@hapi/hoek": "^11.0.2" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "license": "MIT", + "node_modules/@hapi/statehood": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-8.2.1.tgz", + "integrity": "sha512-xf72TG/QINW26jUu+uL5H+crE1o8GplIgfPWwPZhnAGJzetIVAQEQYvzq+C0aEVHg5/lMMtQ+L9UryuSa5Yjkg==", + "license": "BSD-3-Clause", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "@hapi/boom": "^10.0.1", + "@hapi/bounce": "^3.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/cryptiles": "^6.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/iron": "^7.0.1", + "@hapi/validate": "^2.0.1" } }, - "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "node_modules/@hapi/subtext": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-8.1.3.tgz", + "integrity": "sha512-WTpEZQjBP3UJ3gGunNl3w5Ao1EOJsuu2vttZ2KEcG+csSLxc0dI6VIkl2md2jDlHiQ2ARAoqdSUScy05A/NHtA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/content": "^6.0.2", + "@hapi/file": "^3.0.0", + "@hapi/hoek": "^11.0.7", + "@hapi/pez": "^6.1.1", + "@hapi/wreck": "^18.1.1" + } + }, + "node_modules/@hapi/teamwork": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-6.0.1.tgz", + "integrity": "sha512-52OXRslUfYwXAOG8k58f2h2ngXYQGP0x5RPOo+eWA/FtyLgHjGMrE3+e9LSXP/0q2YfHAK5wj9aA9DTy1K+kyQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" + } + }, + "node_modules/@hapi/vise": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-5.0.1.tgz", + "integrity": "sha512-XZYWzzRtINQLedPYlIkSkUr7m5Ddwlu99V9elh8CSygXstfv3UnWIXT0QD+wmR0VAG34d2Vx3olqcEhRRoTu9A==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/wreck": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.1.1.tgz", + "integrity": "sha512-UwTeGBfAnB/1mkw4gD6IQGI/bgMu7iGmqgT8K+xxye3z4ZHhCZlmS2wuHBJmENhBJSKqvoYzJ71ds3Xfq4gofQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@prisma/adapter-mariadb": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-mariadb/-/adapter-mariadb-7.8.0.tgz", + "integrity": "sha512-mWsgcfbUjxB3qSzRlLs8E03vsKrqXzYK2zpx3e8u6wIgeHJM/sE46cuOGcYvHiZGmeQLCd3xL6YSSGM9QOLI6w==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/driver-adapter-utils": "7.8.0", + "mariadb": "3.4.5" + } + }, + "node_modules/@prisma/client": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz", + "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/client-runtime-utils": "7.8.0" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/client-runtime-utils": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.8.0.tgz", + "integrity": "sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/config": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.8.0.tgz", + "integrity": "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.3.4", + "deepmerge-ts": "7.1.5", + "effect": "3.20.0", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz", + "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/dev": { + "version": "0.24.3", + "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.3.tgz", + "integrity": "sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "@electric-sql/pglite": "0.4.1", + "@electric-sql/pglite-socket": "0.1.1", + "@electric-sql/pglite-tools": "0.3.1", + "@hono/node-server": "1.19.11", + "@prisma/get-platform": "7.2.0", + "@prisma/query-plan-executor": "7.2.0", + "@prisma/streams-local": "0.1.2", + "foreground-child": "3.3.1", + "get-port-please": "3.2.0", + "hono": "^4.12.8", + "http-status-codes": "2.3.0", + "pathe": "2.0.3", + "proper-lockfile": "4.1.2", + "remeda": "2.33.4", + "std-env": "3.10.0", + "valibot": "1.2.0", + "zeptomatch": "2.1.0" + } + }, + "node_modules/@prisma/driver-adapter-utils": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz", + "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.8.0" + } + }, + "node_modules/@prisma/engines": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz", + "integrity": "sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.8.0", + "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "@prisma/fetch-engine": "7.8.0", + "@prisma/get-platform": "7.8.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a.tgz", + "integrity": "sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz", + "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.8.0" + } + }, + "node_modules/@prisma/fetch-engine": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.8.0.tgz", + "integrity": "sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.8.0", + "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", + "@prisma/get-platform": "7.8.0" + } + }, + "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz", + "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.8.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", + "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.2.0" + } + }, + "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", + "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/query-plan-executor": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz", + "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/streams-local": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.2.tgz", + "integrity": "sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "ajv": "^8.12.0", + "better-result": "^2.7.0", + "env-paths": "^3.0.0", + "proper-lockfile": "^4.1.2" + }, + "engines": { + "bun": ">=1.3.6", + "node": ">=22.0.0" + } + }, + "node_modules/@prisma/studio-core": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.27.3.tgz", + "integrity": "sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@radix-ui/react-toggle": "1.1.10", + "chart.js": "4.5.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0", + "pnpm": "8" + }, + "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsoa/cli": { + "version": "7.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/@tsoa/cli/-/cli-7.0.0-alpha.0.tgz", + "integrity": "sha512-fCBWv6F20qrpwFh5X+vaZs38Bh/5cVwKPhvG/uArR+crZgEowyNl76unLCmYdvycES9Y6g/TKFLagG8qojSNuQ==", + "license": "MIT", + "dependencies": { + "@tsoa/runtime": "^7.0.0-alpha.0", + "@types/multer": "^1.4.12", + "fs-extra": "^11.2.0", + "glob": "^10.3.10", + "handlebars": "^4.7.8", + "merge-anything": "^5.1.7", + "minimatch": "^9.0.1", + "ts-deepmerge": "^7.0.2", + "typescript": "^5.7.2", + "validator": "^13.12.0", + "yaml": "^2.6.1", + "yargs": "^17.7.1" + }, + "bin": { + "tsoa": "dist/cli.js" + }, + "engines": { + "node": ">=18.0.0", + "yarn": ">=1.9.4" + } + }, + "node_modules/@tsoa/cli/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/@tsoa/cli/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tsoa/cli/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tsoa/runtime": { + "version": "7.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/@tsoa/runtime/-/runtime-7.0.0-alpha.0.tgz", + "integrity": "sha512-zlWYz2bLfaN6WtFoIbLBEAyVhKG4IQKJ9QPzeRFFKeAsh5zYN4/ocnd14XyWs4ehY9TdtTnma2drW89aYNSRYw==", + "license": "MIT", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hapi": "^21.3.12", + "@types/koa": "^2.15.0", + "@types/multer": "^1.4.12", + "express": "^4.21.2", + "reflect-metadata": "^0.2.2", + "validator": "^13.12.0" + }, + "engines": { + "node": ">=18.0.0", + "yarn": ">=1.9.4" + } + }, + "node_modules/@tsoa/runtime/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@tsoa/runtime/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/@tsoa/runtime/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@tsoa/runtime/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/@tsoa/runtime/node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@tsoa/runtime/node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@tsoa/runtime/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@tsoa/runtime/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tsoa/runtime/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/@tsoa/runtime/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@tsoa/runtime/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/@tsoa/runtime/node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@tsoa/runtime/node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@tsoa/runtime/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/content-disposition": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.9.tgz", + "integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==", + "license": "MIT" + }, + "node_modules/@types/cookie-parser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", + "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/cookies": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.2.tgz", + "integrity": "sha512-1AvkDdZM2dbyFybL4fxpuNCaWyv//0AwsuUk2DWeXyM1/5ZKm6W3z6mQi24RZ4l2ucY+bkSHzbDVpySqPGuV8A==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/http-assert": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz", + "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==", + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", + "license": "MIT" + }, + "node_modules/@types/koa": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", + "license": "MIT", + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "node_modules/@types/koa-compose": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.9.tgz", + "integrity": "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA==", + "license": "MIT", + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/morgan": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", + "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/multer": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", + "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/node": { + "version": "24.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", + "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/better-result": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.9.2.tgz", + "integrity": "sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c12": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", "engines": { "node": ">=18" @@ -719,6 +2258,25 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", @@ -728,6 +2286,45 @@ "node": ">=6.6.0" } }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT", + "peer": true + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -745,6 +2342,32 @@ } } }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -754,6 +2377,35 @@ "node": ">= 0.8" } }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -768,12 +2420,45 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/effect": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz", + "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -783,6 +2468,19 @@ "node": ">= 0.8" } }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -814,9 +2512,10 @@ } }, "node_modules/esbuild": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", - "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -826,32 +2525,41 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.4", - "@esbuild/android-arm": "0.27.4", - "@esbuild/android-arm64": "0.27.4", - "@esbuild/android-x64": "0.27.4", - "@esbuild/darwin-arm64": "0.27.4", - "@esbuild/darwin-x64": "0.27.4", - "@esbuild/freebsd-arm64": "0.27.4", - "@esbuild/freebsd-x64": "0.27.4", - "@esbuild/linux-arm": "0.27.4", - "@esbuild/linux-arm64": "0.27.4", - "@esbuild/linux-ia32": "0.27.4", - "@esbuild/linux-loong64": "0.27.4", - "@esbuild/linux-mips64el": "0.27.4", - "@esbuild/linux-ppc64": "0.27.4", - "@esbuild/linux-riscv64": "0.27.4", - "@esbuild/linux-s390x": "0.27.4", - "@esbuild/linux-x64": "0.27.4", - "@esbuild/netbsd-arm64": "0.27.4", - "@esbuild/netbsd-x64": "0.27.4", - "@esbuild/openbsd-arm64": "0.27.4", - "@esbuild/openbsd-x64": "0.27.4", - "@esbuild/openharmony-arm64": "0.27.4", - "@esbuild/sunos-x64": "0.27.4", - "@esbuild/win32-arm64": "0.27.4", - "@esbuild/win32-ia32": "0.27.4", - "@esbuild/win32-x64": "0.27.4" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" } }, "node_modules/escape-html": { @@ -912,10 +2620,65 @@ "url": "https://opencollective.com/express" } }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -945,6 +2708,22 @@ "url": "https://opencollective.com/express" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -963,10 +2742,25 @@ "node": ">= 0.8" } }, + "node_modules/fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -986,6 +2780,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1010,6 +2823,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-port-please": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", + "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", + "devOptional": true, + "license": "MIT" + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -1024,9 +2844,10 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -1035,10 +2856,42 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/giget": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz", + "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==", + "devOptional": true, + "license": "MIT", + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -1047,6 +2900,36 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1059,10 +2942,52 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/grammex": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz", + "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/graphmatch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz", + "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -1092,6 +3017,16 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.12.18", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", + "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -1112,6 +3047,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", @@ -1132,6 +3074,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, "license": "ISC" }, "node_modules/inherits": { @@ -1153,6 +3096,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -1165,15 +3109,26 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -1186,6 +3141,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -1197,6 +3153,132 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/lru.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "devOptional": true, + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/mariadb": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz", + "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==", + "license": "LGPL-2.1-or-later", + "dependencies": { + "@types/geojson": "^7946.0.16", + "@types/node": "^24.0.13", + "denque": "^2.1.0", + "iconv-lite": "^0.6.3", + "lru-cache": "^10.4.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/mariadb/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1215,6 +3297,21 @@ "node": ">= 0.8" } }, + "node_modules/merge-anything": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz", + "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -1227,6 +3324,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -1253,12 +3371,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -1267,12 +3386,107 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mysql2": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", + "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -1282,10 +3496,17 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, "node_modules/nodemon": { "version": "3.1.14", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, "license": "MIT", "dependencies": { "chokidar": "^3.5.2", @@ -1314,6 +3535,16 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -1331,6 +3562,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -1343,6 +3581,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1352,6 +3599,12 @@ "wrappy": "1" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1361,20 +3614,60 @@ "node": ">= 0.8" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.1.tgz", + "integrity": "sha512-fvU78fIjZ+SBM9YwCknCvKOUKkLVqtWDVctl0s7xIqfmfb38t2TT4ZU2gHm+Z8xGwgW+QWEU3oQSAzIbo89Ggw==", "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "devOptional": true, + "license": "MIT" + }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -1383,6 +3676,85 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/postgres": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", + "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", + "devOptional": true, + "license": "Unlicense", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, + "node_modules/prisma": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz", + "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "7.8.0", + "@prisma/dev": "0.24.3", + "@prisma/engines": "7.8.0", + "@prisma/studio-core": "0.27.3", + "mysql2": "3.15.3", + "postgres": "3.4.7" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "better-sqlite3": ">=9.0.0", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true, + "license": "ISC" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1400,12 +3772,30 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], "license": "MIT" }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -1441,10 +3831,47 @@ "node": ">= 0.10" } }, + "node_modules/rc9": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", + "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.6", + "destr": "^2.0.5" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "devOptional": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -1453,15 +3880,61 @@ "node": ">=8.10.0" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/remeda": { + "version": "2.33.4", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", + "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/remeda" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -1478,16 +3951,31 @@ "node": ">= 18" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "devOptional": true, + "license": "MIT", + "peer": true + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1522,6 +4010,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", + "devOptional": true + }, "node_modules/serve-static": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", @@ -1547,6 +4041,27 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -1619,10 +4134,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, "license": "MIT", "dependencies": { "semver": "^7.5.3" @@ -1631,6 +4159,25 @@ "node": ">=10" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -1640,10 +4187,114 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^3.0.0" @@ -1656,6 +4307,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -1677,15 +4329,43 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, "license": "ISC", "bin": { "nodetouch": "bin/nodetouch.js" } }, + "node_modules/ts-deepmerge": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-7.0.3.tgz", + "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==", + "license": "ISC", + "engines": { + "node": ">=14.13.1" + } + }, + "node_modules/tsoa": { + "version": "7.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/tsoa/-/tsoa-7.0.0-alpha.0.tgz", + "integrity": "sha512-o5h2DD1IKa2GF728BHYDL2uSX57a44sX8BLFScR4KbD0xlOmgsSDECneJmouDxf9MJdjHHWc+I1S3sGK82B6kQ==", + "license": "MIT", + "dependencies": { + "@tsoa/cli": "^7.0.0-alpha.0", + "@tsoa/runtime": "^7.0.0-alpha.0" + }, + "bin": { + "tsoa": "dist/cli.js" + }, + "engines": { + "node": ">=18.0.0", + "yarn": ">=1.9.4" + } + }, "node_modules/tsx": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "~0.27.0", @@ -1715,19 +4395,54 @@ "node": ">= 0.6" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, "license": "MIT" }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true, + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1737,6 +4452,39 @@ "node": ">= 0.8" } }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/valibot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", + "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1746,11 +4494,226 @@ "node": ">= 0.8" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/zeptomatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", + "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "grammex": "^3.1.11", + "graphmatch": "^1.1.0" + } } } } diff --git a/package.json b/package.json index 76b6bb7..3350a68 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,40 @@ { - "name": "umc_10", + "name": "10th_node", "version": "1.0.0", - "description": "UMC 10기 Node.js 실습 레포입니다.", + "description": "INHA UMC 10th_Node.js 미션, 키워드 인증 레포지터리입니다!", "main": "index.js", "scripts": { - "start:dev": "nodemon --watch '*.ts' --exec tsx ./src/app.ts", - "build": "tsc" + "start": "npx prisma generate --schema=./prisma/schema.prisma && tsoa spec-and-routes && tsx src/index.ts", + "dev": "npx prisma generate --schema=./prisma/schema.prisma && tsoa spec-and-routes && nodemon --exec tsx src/index.ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/inhadissolve/10th_Node.js.git" }, "keywords": [], "author": "", "license": "ISC", - "type": "commonjs", + "type": "module", + "bugs": { + "url": "https://github.com/inhadissolve/10th_Node.js/issues" + }, + "homepage": "https://github.com/inhadissolve/10th_Node.js#readme", "dependencies": { + "@prisma/adapter-mariadb": "^7.8.0", + "@prisma/client": "^7.8.0", + "cookie-parser": "^1.4.7", + "cors": "^2.8.6", + "dotenv": "^17.4.2", "express": "^5.2.1", - "nodemon": "^3.1.14", - "tsx": "^4.21.0" + "morgan": "^1.10.1", + "tsoa": "^7.0.0-alpha.0" }, "devDependencies": { - "@types/express": "^5.0.6" + "@types/cookie-parser": "^1.4.10", + "@types/cors": "^2.8.19", + "@types/morgan": "^1.9.10", + "nodemon": "^3.1.14", + "prisma": "^7.8.0", + "tsx": "^4.21.0" } } diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 0000000..63db9d9 --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,9 @@ +import "dotenv/config"; +import { defineConfig, env } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + datasource: { + url: env("DATABASE_URL"), + }, +}); \ No newline at end of file diff --git a/prisma/migrations/20260506151555_init/migration.sql b/prisma/migrations/20260506151555_init/migration.sql new file mode 100644 index 0000000..529a88e --- /dev/null +++ b/prisma/migrations/20260506151555_init/migration.sql @@ -0,0 +1,126 @@ +-- CreateTable +CREATE TABLE `user` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `email` VARCHAR(255) NOT NULL, + `password` VARCHAR(255) NULL, + `name` VARCHAR(100) NOT NULL, + `gender` VARCHAR(15) NOT NULL, + `birth` DATE NOT NULL, + `address` VARCHAR(255) NOT NULL, + `detail_address` VARCHAR(255) NULL, + `phone_number` VARCHAR(20) NOT NULL, + + UNIQUE INDEX `email`(`email`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `food_category` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `name` VARCHAR(100) NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `user_favor_category` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `user_id` INTEGER NOT NULL, + `food_category_id` INTEGER NOT NULL, + + INDEX `user_id`(`user_id`), + INDEX `f_category_id`(`food_category_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `regions` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `name` VARCHAR(50) NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `stores` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `region_id` INTEGER NOT NULL, + `name` VARCHAR(100) NOT NULL, + `address` VARCHAR(255) NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + INDEX `region_id`(`region_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `reviews` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `user_id` INTEGER NOT NULL, + `store_id` INTEGER NOT NULL, + `rating` INTEGER NOT NULL, + `content` TEXT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + INDEX `user_id`(`user_id`), + INDEX `store_id`(`store_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `missions` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `store_id` INTEGER NOT NULL, + `title` VARCHAR(100) NOT NULL, + `description` TEXT NULL, + `reward_point` INTEGER NOT NULL DEFAULT 0, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + INDEX `store_id`(`store_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `user_missions` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `user_id` INTEGER NOT NULL, + `mission_id` INTEGER NOT NULL, + `status` VARCHAR(20) NOT NULL DEFAULT 'IN_PROGRESS', + `started_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `completed_at` DATETIME(3) NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + INDEX `user_id`(`user_id`), + INDEX `mission_id`(`mission_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `user_favor_category` ADD CONSTRAINT `user_favor_category_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `user_favor_category` ADD CONSTRAINT `user_favor_category_food_category_id_fkey` FOREIGN KEY (`food_category_id`) REFERENCES `food_category`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `stores` ADD CONSTRAINT `stores_region_id_fkey` FOREIGN KEY (`region_id`) REFERENCES `regions`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `reviews` ADD CONSTRAINT `reviews_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `reviews` ADD CONSTRAINT `reviews_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `stores`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `missions` ADD CONSTRAINT `missions_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `stores`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `user_missions` ADD CONSTRAINT `user_missions_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `user_missions` ADD CONSTRAINT `user_missions_mission_id_fkey` FOREIGN KEY (`mission_id`) REFERENCES `missions`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..592fc0b --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "mysql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..e0903d6 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,126 @@ +generator client { + provider = "prisma-client" + output = "../src/generated/prisma" +} + +datasource db { + provider = "mysql" +} + +model User { + id Int @id @default(autoincrement()) + email String @unique(map: "email") @db.VarChar(255) + password String? @db.VarChar(255) + name String @db.VarChar(100) + gender String @db.VarChar(15) + birth DateTime @db.Date + address String @db.VarChar(255) + detailAddress String? @map("detail_address") @db.VarChar(255) + phoneNumber String @map("phone_number") @db.VarChar(20) + + userFavorCategories UserFavorCategory[] + reviews Review[] + userMissions UserMission[] + + @@map("user") +} + +model FoodCategory { + id Int @id @default(autoincrement()) + name String @db.VarChar(100) + + userFavorCategories UserFavorCategory[] + + @@map("food_category") +} + +model UserFavorCategory { + id Int @id @default(autoincrement()) + userId Int @map("user_id") + foodCategoryId Int @map("food_category_id") + + user User @relation(fields: [userId], references: [id]) + foodCategory FoodCategory @relation(fields: [foodCategoryId], references: [id]) + + @@index([userId], map: "user_id") + @@index([foodCategoryId], map: "f_category_id") + @@map("user_favor_category") +} + +model Region { + id Int @id @default(autoincrement()) + name String @db.VarChar(50) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @map("updated_at") + + stores Store[] + + @@map("regions") +} + +model Store { + id Int @id @default(autoincrement()) + regionId Int @map("region_id") + name String @db.VarChar(100) + address String @db.VarChar(255) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @map("updated_at") + + region Region @relation(fields: [regionId], references: [id]) + reviews Review[] + missions Mission[] + + @@index([regionId], map: "region_id") + @@map("stores") +} + +model Review { + id Int @id @default(autoincrement()) + userId Int @map("user_id") + storeId Int @map("store_id") + rating Int + content String? @db.Text + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @map("updated_at") + + user User @relation(fields: [userId], references: [id]) + store Store @relation(fields: [storeId], references: [id]) + + @@index([userId], map: "user_id") + @@index([storeId], map: "store_id") + @@map("reviews") +} + +model Mission { + id Int @id @default(autoincrement()) + storeId Int @map("store_id") + title String @db.VarChar(100) + description String? @db.Text + rewardPoint Int @default(0) @map("reward_point") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @map("updated_at") + + store Store @relation(fields: [storeId], references: [id]) + userMissions UserMission[] + + @@index([storeId], map: "store_id") + @@map("missions") +} + +model UserMission { + id Int @id @default(autoincrement()) + userId Int @map("user_id") + missionId Int @map("mission_id") + status String @default("IN_PROGRESS") @db.VarChar(20) + startedAt DateTime @default(now()) @map("started_at") + completedAt DateTime? @map("completed_at") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @map("updated_at") + + user User @relation(fields: [userId], references: [id]) + mission Mission @relation(fields: [missionId], references: [id]) + + @@index([userId], map: "user_id") + @@index([missionId], map: "mission_id") + @@map("user_missions") +} \ No newline at end of file diff --git a/src/common/errors/app.error.ts b/src/common/errors/app.error.ts new file mode 100644 index 0000000..2b2d955 --- /dev/null +++ b/src/common/errors/app.error.ts @@ -0,0 +1,18 @@ +export class AppError extends Error { + public readonly errorCode: string; + public readonly statusCode: number; + public readonly data?: unknown; + + constructor(params: { + errorCode: string; + message: string; + statusCode: number; + data?: unknown; + }) { + super(params.message); + + this.errorCode = params.errorCode; + this.statusCode = params.statusCode; + this.data = params.data ?? null; + } +} \ No newline at end of file diff --git a/src/common/errors/user.error.ts b/src/common/errors/user.error.ts new file mode 100644 index 0000000..0202ff8 --- /dev/null +++ b/src/common/errors/user.error.ts @@ -0,0 +1,12 @@ +import { AppError } from "./app.error.js"; + +export class DuplicateUserEmailError extends AppError { + constructor(data?: unknown) { + super({ + errorCode: "U001", + message: "이미 존재하는 이메일입니다.", + statusCode: 409, + data, + }); + } +} \ No newline at end of file diff --git a/src/common/middlewares/auth.middleware.ts b/src/common/middlewares/auth.middleware.ts new file mode 100644 index 0000000..deab7dd --- /dev/null +++ b/src/common/middlewares/auth.middleware.ts @@ -0,0 +1,21 @@ +import { Request, Response, NextFunction } from "express"; + +export function authorizeUser() { + return async (req: Request, res: Response, next: NextFunction) => { + const { username } = req.cookies; + + if (username) { + console.log(`[인증 성공] ${username}님, 환영합니다.`); + next(); + return; + } + + console.log("[인증 실패] 로그인이 필요합니다."); + + res + .status(401) + .send( + '' + ); + }; +} \ No newline at end of file diff --git a/src/common/responses/response.ts b/src/common/responses/response.ts new file mode 100644 index 0000000..9469334 --- /dev/null +++ b/src/common/responses/response.ts @@ -0,0 +1,11 @@ +export interface ApiResponse { + resultType: "SUCCESS"; + error: null; + success: T; +} + +export const success = (data: T): ApiResponse => ({ + resultType: "SUCCESS", + error: null, + success: data, +}); \ No newline at end of file diff --git a/src/db.config.ts b/src/db.config.ts new file mode 100644 index 0000000..08b3acd --- /dev/null +++ b/src/db.config.ts @@ -0,0 +1,17 @@ +import "dotenv/config"; +import { PrismaClient } from "./generated/prisma/client.js"; +import { PrismaMariaDb } from "@prisma/adapter-mariadb"; + +const adapter = new PrismaMariaDb({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : 3306, + connectionLimit: 10, +}); + +export const prisma = new PrismaClient({ + adapter, + log: ["query", "info", "warn", "error"], +}); \ No newline at end of file diff --git a/src/generated/routes.ts b/src/generated/routes.ts new file mode 100644 index 0000000..9fff9b6 --- /dev/null +++ b/src/generated/routes.ts @@ -0,0 +1,252 @@ +/* tslint:disable */ +/* eslint-disable */ +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import type { TsoaRoute } from '@tsoa/runtime'; +import { fetchMiddlewares, ExpressTemplateService } from '@tsoa/runtime'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { UserController } from './../modules/users/controllers/user.controller'; +import type { Request as ExRequest, Response as ExResponse, RequestHandler, Router } from 'express'; + + + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + +const models: TsoaRoute.Models = { + "UserSignUpResponse": { + "dataType": "refObject", + "properties": { + "userId": {"dataType":"double","required":true}, + "preferences": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ApiResponse_UserSignUpResponse_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"enum","enums":["SUCCESS"],"required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "success": {"ref":"UserSignUpResponse","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "UserSignUpRequest": { + "dataType": "refObject", + "properties": { + "email": {"dataType":"string","required":true}, + "password": {"dataType":"string"}, + "name": {"dataType":"string","required":true}, + "gender": {"dataType":"string","required":true}, + "birth": {"dataType":"string","required":true}, + "address": {"dataType":"string"}, + "detailAddress": {"dataType":"string"}, + "phoneNumber": {"dataType":"string","required":true}, + "preferences": {"dataType":"array","array":{"dataType":"double"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +}; +const templateService = new ExpressTemplateService(models, {"noImplicitAdditionalProperties":"throw-on-extras","bodyCoercion":true}); + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + + + +export function RegisterRoutes(app: Router) { + + // ########################################################################################################### + // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look + // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa + // ########################################################################################################### + + + + const argsUserController_handleUserSignUp: Record = { + body: {"in":"body","name":"body","required":true,"ref":"UserSignUpRequest"}, + }; + app.post('/users/signup', + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.handleUserSignUp)), + + async function UserController_handleUserSignUp(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleUserSignUp, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'handleUserSignUp', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsUserController_handleGuestPage: Record = { + }; + app.get('/users/guest', + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.handleGuestPage)), + + async function UserController_handleGuestPage(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleGuestPage, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'handleGuestPage', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsUserController_handleLoginPage: Record = { + }; + app.get('/users/login', + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.handleLoginPage)), + + async function UserController_handleLoginPage(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleLoginPage, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'handleLoginPage', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsUserController_handleMypage: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.get('/users/mypage', + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.handleMypage)), + + async function UserController_handleMypage(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleMypage, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'handleMypage', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsUserController_handleSetLogin: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.get('/users/set-login', + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.handleSetLogin)), + + async function UserController_handleSetLogin(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleSetLogin, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'handleSetLogin', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsUserController_handleSetLogout: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.get('/users/set-logout', + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.handleSetLogout)), + + async function UserController_handleSetLogout(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleSetLogout, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'handleSetLogout', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +} + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e984fbb --- /dev/null +++ b/src/index.ts @@ -0,0 +1,65 @@ +import dotenv from "dotenv"; +import express, { Express, NextFunction, Request, Response } from "express"; +import cors from "cors"; +import morgan from "morgan"; +import cookieParser from "cookie-parser"; + +import { RegisterRoutes } from "./generated/routes.js"; +import { AppError } from "./common/errors/app.error.js"; + +dotenv.config(); + +const app: Express = express(); +const port = process.env.PORT || 3000; + +/** + * 공통 미들웨어 설정 + */ +app.use(cors()); +app.use(morgan("dev")); +app.use(cookieParser()); +app.use(express.static("public")); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); + +/** + * 기본 서버 확인용 API + */ +app.get("/", (req: Request, res: Response) => { + res.send("Hello World! This is TypeScript Server!"); +}); + +/** + * Tsoa가 자동 생성한 routes 등록 + */ +const router = express.Router(); + +RegisterRoutes(router); + +app.use("/api/v1", router); + +/** + * 전역 에러 핸들러 + */ +app.use((err: AppError, req: Request, res: Response, next: NextFunction) => { + if (res.headersSent) { + return next(err); + } + + res.status(err.statusCode || 500).json({ + resultType: "FAIL", + error: { + errorCode: err.errorCode || "UNKNOWN", + reason: err.message || "서버 오류가 발생했습니다.", + data: err.data || null, + }, + success: null, + }); +}); + +/** + * 서버 실행 + */ +app.listen(port, () => { + console.log(`[server]: Server is running at http://localhost:${port}`); +}); \ No newline at end of file diff --git a/src/modules/missions/controllers/mission.controller.ts b/src/modules/missions/controllers/mission.controller.ts new file mode 100644 index 0000000..e56a675 --- /dev/null +++ b/src/modules/missions/controllers/mission.controller.ts @@ -0,0 +1,153 @@ +import { Request, Response, NextFunction } from "express"; +import { StatusCodes } from "http-status-codes"; +import { + bodyToMission, + bodyToMissionChallenge, +} from "../dtos/mission.dto.js"; + +import { + createMission, + challengeMission, + listStoreMissions, + listInProgressMissions, + completeMission, +} from "../services/mission.service.js"; + +export const handleCreateMission = async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + console.log("미션 추가를 요청했습니다."); + console.log("params:", req.params); + console.log("body:", req.body); + + const storeId = Number(req.params.storeId); + + if (Number.isNaN(storeId)) { + throw new Error("storeId는 숫자여야 합니다."); + } + + const mission = await createMission(bodyToMission(req.body, storeId)); + + res.status(StatusCodes.CREATED).json({ + result: mission, + }); + } catch (error) { + next(error); + } +}; + +export const handleChallengeMission = async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + console.log("미션 도전을 요청했습니다."); + console.log("params:", req.params); + console.log("body:", req.body); + + const missionId = Number(req.params.missionId); + + if (Number.isNaN(missionId)) { + throw new Error("missionId는 숫자여야 합니다."); + } + + const userMission = await challengeMission( + bodyToMissionChallenge(req.body, missionId) + ); + + res.status(StatusCodes.CREATED).json({ + result: userMission, + }); + } catch (error) { + next(error); + } +}; + +export const handleListStoreMissions = async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + const storeId = Number(req.params.storeId); + + const cursor = + typeof req.query.cursor === "string" + ? Number(req.query.cursor) + : 0; + + if (Number.isNaN(storeId)) { + throw new Error("storeId는 숫자여야 합니다."); + } + + if (Number.isNaN(cursor)) { + throw new Error("cursor는 숫자여야 합니다."); + } + + const missions = await listStoreMissions(storeId, cursor); + + res.status(StatusCodes.OK).json(missions); + } catch (error) { + next(error); + } +}; + +export const handleListInProgressMissions = async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + const userId = Number(req.params.userId); + + const cursor = + typeof req.query.cursor === "string" + ? Number(req.query.cursor) + : 0; + + if (Number.isNaN(userId)) { + throw new Error("userId는 숫자여야 합니다."); + } + + if (Number.isNaN(cursor)) { + throw new Error("cursor는 숫자여야 합니다."); + } + + const missions = await listInProgressMissions(userId, cursor); + + res.status(StatusCodes.OK).json(missions); + } catch (error) { + next(error); + } +}; + +export const handleCompleteMission = async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + const userId = Number(req.params.userId); + const missionId = Number(req.params.missionId); + + if (Number.isNaN(userId)) { + throw new Error("userId는 숫자여야 합니다."); + } + + if (Number.isNaN(missionId)) { + throw new Error("missionId는 숫자여야 합니다."); + } + + const result = await completeMission(userId, missionId); + + res.status(StatusCodes.OK).json({ + result, + }); + } catch (error) { + next(error); + } +}; \ No newline at end of file diff --git a/src/modules/missions/dtos/mission.dto.ts b/src/modules/missions/dtos/mission.dto.ts new file mode 100644 index 0000000..d71422d --- /dev/null +++ b/src/modules/missions/dtos/mission.dto.ts @@ -0,0 +1,108 @@ +export interface MissionCreateRequest { + title: string; + description: string; + rewardPoint: number; +} + +export const bodyToMission = ( + body: MissionCreateRequest, + storeId: number +) => { + return { + storeId, + title: body.title, + description: body.description, + rewardPoint: body.rewardPoint, + }; +}; + +export const responseFromMission = (mission: any) => { + return { + missionId: mission.id, + storeId: mission.store_id, + title: mission.title, + description: mission.description, + rewardPoint: mission.reward_point, + }; +}; + +export interface MissionChallengeRequest { + userId: number; +} + +export const bodyToMissionChallenge = ( + body: MissionChallengeRequest, + missionId: number +) => { + return { + userId: body.userId, + missionId, + }; +}; + +export const responseFromUserMission = (userMission: any) => { + return { + userMissionId: userMission.id, + userId: userMission.user_id, + missionId: userMission.mission_id, + status: userMission.status, + }; +}; + +export const responseFromStoreMissions = (missions: any[]) => { + const lastMission = missions[missions.length - 1]; + + return { + data: missions.map((mission) => ({ + missionId: mission.id, + title: mission.title, + description: mission.description, + rewardPoint: mission.rewardPoint, + createdAt: mission.createdAt, + store: { + storeId: mission.store.id, + name: mission.store.name, + address: mission.store.address, + }, + })), + pagination: { + cursor: lastMission ? lastMission.id : null, + }, + }; +}; +export const responseFromInProgressMissions = (userMissions: any[]) => { + const lastUserMission = userMissions[userMissions.length - 1]; + + return { + data: userMissions.map((userMission) => ({ + userMissionId: userMission.id, + status: userMission.status, + startedAt: userMission.startedAt, + mission: { + missionId: userMission.mission.id, + title: userMission.mission.title, + description: userMission.mission.description, + rewardPoint: userMission.mission.rewardPoint, + store: { + storeId: userMission.mission.store.id, + name: userMission.mission.store.name, + address: userMission.mission.store.address, + }, + }, + })), + pagination: { + cursor: lastUserMission ? lastUserMission.id : null, + }, + }; +}; + +export const responseFromCompletedMission = (userMission: any) => { + return { + userMissionId: userMission.id, + userId: userMission.userId, + missionId: userMission.missionId, + status: userMission.status, + startedAt: userMission.startedAt, + completedAt: userMission.completedAt, + }; +}; \ No newline at end of file diff --git a/src/modules/missions/repositories/mission.repository.ts b/src/modules/missions/repositories/mission.repository.ts new file mode 100644 index 0000000..e26f4f8 --- /dev/null +++ b/src/modules/missions/repositories/mission.repository.ts @@ -0,0 +1,175 @@ +import { prisma } from "../../../db.config.js"; + +export const findStoreById = async (storeId: number) => { + return await prisma.store.findFirst({ + where: { + id: storeId, + }, + }); +}; + +export const addMission = async (data: { + storeId: number; + title: string; + description: string; + rewardPoint: number; +}) => { + const createdMission = await prisma.mission.create({ + data: { + storeId: data.storeId, + title: data.title, + description: data.description, + rewardPoint: data.rewardPoint, + }, + }); + + return createdMission.id; +}; + +export const findMissionById = async (missionId: number) => { + return await prisma.mission.findFirst({ + where: { + id: missionId, + }, + }); +}; + +export const findUserById = async (userId: number) => { + return await prisma.user.findFirst({ + where: { + id: userId, + }, + }); +}; + +export const findUserMission = async ( + userId: number, + missionId: number +) => { + return await prisma.userMission.findFirst({ + where: { + userId, + missionId, + status: "IN_PROGRESS", + }, + }); +}; + +export const addUserMission = async (data: { + userId: number; + missionId: number; +}) => { + const createdUserMission = await prisma.userMission.create({ + data: { + userId: data.userId, + missionId: data.missionId, + status: "IN_PROGRESS", + }, + }); + + return createdUserMission.id; +}; + +export const findUserMissionById = async (userMissionId: number) => { + return await prisma.userMission.findFirst({ + where: { + id: userMissionId, + }, + }); +}; + +export const getMissionsByStoreId = async ( + storeId: number, + cursor: number +) => { + return await prisma.mission.findMany({ + where: { + storeId, + id: { + gt: cursor, + }, + }, + select: { + id: true, + title: true, + description: true, + rewardPoint: true, + createdAt: true, + store: { + select: { + id: true, + name: true, + address: true, + }, + }, + }, + orderBy: { + id: "asc", + }, + take: 5, + }); +}; +export const getInProgressMissionsByUserId = async ( + userId: number, + cursor: number +) => { + return await prisma.userMission.findMany({ + where: { + userId, + status: "IN_PROGRESS", + id: { + gt: cursor, + }, + }, + select: { + id: true, + status: true, + startedAt: true, + mission: { + select: { + id: true, + title: true, + description: true, + rewardPoint: true, + store: { + select: { + id: true, + name: true, + address: true, + }, + }, + }, + }, + }, + orderBy: { + id: "asc", + }, + take: 5, + }); +}; + +export const findInProgressUserMission = async ( + userId: number, + missionId: number +) => { + return await prisma.userMission.findFirst({ + where: { + userId, + missionId, + status: "IN_PROGRESS", + }, + }); +}; + +export const completeUserMission = async (userMissionId: number) => { + return await prisma.userMission.update({ + where: { + id: userMissionId, + }, + data: { + status: "COMPLETED", + completedAt: new Date(), + updatedAt: new Date(), + }, + }); +}; \ No newline at end of file diff --git a/src/modules/missions/services/mission.service.ts b/src/modules/missions/services/mission.service.ts new file mode 100644 index 0000000..39f9588 --- /dev/null +++ b/src/modules/missions/services/mission.service.ts @@ -0,0 +1,132 @@ +import { + addMission, + addUserMission, + findMissionById, + findStoreById, + findUserById, + findUserMission, + findUserMissionById, + getMissionsByStoreId, + getInProgressMissionsByUserId, + findInProgressUserMission, + completeUserMission, +} from "../repositories/mission.repository.js"; +import { + responseFromMission, + responseFromUserMission, + responseFromStoreMissions, + responseFromInProgressMissions, + responseFromCompletedMission, +} from "../dtos/mission.dto.js"; + +export const createMission = async (data: { + storeId: number; + title: string; + description: string; + rewardPoint: number; +}) => { + const store = await findStoreById(data.storeId); + + if (!store) { + throw new Error("존재하지 않는 가게입니다."); + } + + if (data.rewardPoint < 0) { + throw new Error("보상 포인트는 0 이상이어야 합니다."); + } + + const missionId = await addMission(data); + + const mission = await findMissionById(missionId); + + return responseFromMission(mission); +}; + +export const challengeMission = async (data: { + userId: number; + missionId: number; +}) => { + const user = await findUserById(data.userId); + + if (!user) { + throw new Error("존재하지 않는 사용자입니다."); + } + + const mission = await findMissionById(data.missionId); + + if (!mission) { + throw new Error("존재하지 않는 미션입니다."); + } + + const alreadyChallenged = await findUserMission( + data.userId, + data.missionId + ); + + if (alreadyChallenged) { + throw new Error("이미 도전 중인 미션입니다."); + } + + const userMissionId = await addUserMission(data); + + const userMission = await findUserMissionById(userMissionId); + + return responseFromUserMission(userMission); +}; + +export const listStoreMissions = async ( + storeId: number, + cursor: number +) => { + const store = await findStoreById(storeId); + + if (!store) { + throw new Error("존재하지 않는 가게입니다."); + } + + const missions = await getMissionsByStoreId(storeId, cursor); + + return responseFromStoreMissions(missions); +}; + +export const listInProgressMissions = async ( + userId: number, + cursor: number +) => { + const user = await findUserById(userId); + + if (!user) { + throw new Error("존재하지 않는 사용자입니다."); + } + + const missions = await getInProgressMissionsByUserId(userId, cursor); + + return responseFromInProgressMissions(missions); +}; + +export const completeMission = async ( + userId: number, + missionId: number +) => { + const user = await findUserById(userId); + + if (!user) { + throw new Error("존재하지 않는 사용자입니다."); + } + + const mission = await findMissionById(missionId); + + if (!mission) { + throw new Error("존재하지 않는 미션입니다."); + } + + const userMission = await findInProgressUserMission(userId, missionId); + + if (!userMission) { + throw new Error("진행 중인 미션이 아닙니다."); + } + + const completedMission = await completeUserMission(userMission.id); + + return responseFromCompletedMission(completedMission); +}; \ No newline at end of file diff --git a/src/modules/reviews/controllers/review.controller.ts b/src/modules/reviews/controllers/review.controller.ts new file mode 100644 index 0000000..66c4d34 --- /dev/null +++ b/src/modules/reviews/controllers/review.controller.ts @@ -0,0 +1,62 @@ +import { Request, Response, NextFunction } from "express"; +import { StatusCodes } from "http-status-codes"; +import { bodyToReview } from "../dtos/review.dto.js"; +import { + createReview, + listMyReviews, +} from "../services/review.service.js"; + +export const handleCreateReview = async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + console.log("리뷰 추가를 요청했습니다."); + console.log("params:", req.params); + console.log("body:", req.body); + + const storeId = Number(req.params.storeId); + + if (Number.isNaN(storeId)) { + throw new Error("storeId는 숫자여야 합니다."); + } + + const review = await createReview(bodyToReview(req.body, storeId)); + + res.status(StatusCodes.CREATED).json({ + result: review, + }); + } catch (error) { + next(error); + } +}; + +export const handleListMyReviews = async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + const userId = Number(req.params.userId); + + const cursor = + typeof req.query.cursor === "string" + ? Number(req.query.cursor) + : 0; + + if (Number.isNaN(userId)) { + throw new Error("userId는 숫자여야 합니다."); + } + + if (Number.isNaN(cursor)) { + throw new Error("cursor는 숫자여야 합니다."); + } + + const reviews = await listMyReviews(userId, cursor); + + res.status(StatusCodes.OK).json(reviews); + } catch (error) { + next(error); + } +}; \ No newline at end of file diff --git a/src/modules/reviews/dtos/review.dto.ts b/src/modules/reviews/dtos/review.dto.ts new file mode 100644 index 0000000..7511f35 --- /dev/null +++ b/src/modules/reviews/dtos/review.dto.ts @@ -0,0 +1,48 @@ +export interface ReviewCreateRequest { + rating: number; + content: string; +} + +export const bodyToReview = ( + body: ReviewCreateRequest, + storeId: number +) => { + return { + storeId, + rating: body.rating, + content: body.content, + }; +}; + +export const responseFromReview = (review: any) => { + return { + reviewId: review.id, + storeId: review.store_id, + rating: review.rating, + content: review.content, + }; +}; + +export const responseFromMyReviews = (reviews: any[]) => { + const lastReview = reviews[reviews.length - 1]; + + return { + data: reviews.map((review) => ({ + reviewId: review.id, + rating: review.rating, + content: review.content, + createdAt: review.createdAt, + store: { + storeId: review.store.id, + name: review.store.name, + address: review.store.address, + }, + })), + + pagination: { + // 다음 요청에서 사용할 cursor + // 리뷰가 없으면 null 반환 + cursor: lastReview ? lastReview.id : null, + }, + }; +}; \ No newline at end of file diff --git a/src/modules/reviews/repositories/review.repository.ts b/src/modules/reviews/repositories/review.repository.ts new file mode 100644 index 0000000..c887e87 --- /dev/null +++ b/src/modules/reviews/repositories/review.repository.ts @@ -0,0 +1,79 @@ +import { prisma } from "../../../db.config.js"; + +export const findStoreById = async (storeId: number) => { + return await prisma.store.findFirst({ + where: { + id: storeId, + }, + }); +}; + +export const addReview = async (data: { + storeId: number; + rating: number; + content: string; + userId?: number; +}) => { + const createdReview = await prisma.review.create({ + data: { + storeId: data.storeId, + + // 기존 5주차 리뷰 API는 userId를 받지 않았기 때문에 + // 임시로 userId가 없으면 1번 사용자를 사용하도록 처리 + userId: data.userId ?? 1, + + rating: data.rating, + content: data.content, + }, + }); + + return createdReview.id; +}; + +export const findReviewById = async (reviewId: number) => { + return await prisma.review.findFirst({ + where: { + id: reviewId, + }, + }); +}; + +export const getReviewsByUserId = async ( + userId: number, + cursor: number +) => { + return await prisma.review.findMany({ + where: { + userId, + + // cursor보다 큰 id를 가진 리뷰만 조회 + id: { + gt: cursor, + }, + }, + + select: { + id: true, + rating: true, + content: true, + createdAt: true, + + // 리뷰가 작성된 가게 정보도 함께 가져옴 + store: { + select: { + id: true, + name: true, + address: true, + }, + }, + }, + + // id가 작은 순서대로 조회 + orderBy: { + id: "asc", + }, + + // 한 번에 5개만 조회 + take: 5, + }); +}; \ No newline at end of file diff --git a/src/modules/reviews/services/review.service.ts b/src/modules/reviews/services/review.service.ts new file mode 100644 index 0000000..0dd21b6 --- /dev/null +++ b/src/modules/reviews/services/review.service.ts @@ -0,0 +1,42 @@ +import { + addReview, + findReviewById, + findStoreById, + getReviewsByUserId, +} from "../repositories/review.repository.js"; + +import { + responseFromReview, + responseFromMyReviews, +} from "../dtos/review.dto.js"; + +export const createReview = async (data: { + storeId: number; + rating: number; + content: string; +}) => { + const store = await findStoreById(data.storeId); + + if (!store) { + throw new Error("존재하지 않는 가게입니다."); + } + + if (data.rating < 1 || data.rating > 5) { + throw new Error("평점은 1점부터 5점 사이여야 합니다."); + } + + const reviewId = await addReview(data); + + const review = await findReviewById(reviewId); + + return responseFromReview(review); +}; + +export const listMyReviews = async ( + userId: number, + cursor: number +) => { + const reviews = await getReviewsByUserId(userId, cursor); + + return responseFromMyReviews(reviews); +}; \ No newline at end of file diff --git a/src/modules/stores/controllers/store.controller.ts b/src/modules/stores/controllers/store.controller.ts new file mode 100644 index 0000000..75e93e4 --- /dev/null +++ b/src/modules/stores/controllers/store.controller.ts @@ -0,0 +1,30 @@ +import { Request, Response, NextFunction } from "express"; +import { StatusCodes } from "http-status-codes"; +import { bodyToStore } from "../dtos/store.dto.js"; +import { createStore } from "../services/store.service.js"; + +export const handleCreateStore = async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + console.log("가게 추가를 요청했습니다."); + console.log("params:", req.params); + console.log("body:", req.body); + + const regionId = Number(req.params.regionId); + + if (Number.isNaN(regionId)) { + throw new Error("regionId는 숫자여야 합니다."); + } + + const store = await createStore(bodyToStore(req.body, regionId)); + + res.status(StatusCodes.CREATED).json({ + result: store, + }); + } catch (error) { + next(error); + } +}; \ No newline at end of file diff --git a/src/modules/stores/dtos/store.dto.ts b/src/modules/stores/dtos/store.dto.ts new file mode 100644 index 0000000..236cc3c --- /dev/null +++ b/src/modules/stores/dtos/store.dto.ts @@ -0,0 +1,21 @@ +export interface StoreCreateRequest { + name: string; + address: string; +} + +export const bodyToStore = (body: StoreCreateRequest, regionId: number) => { + return { + regionId, + name: body.name, + address: body.address, + }; +}; + +export const responseFromStore = (store: any) => { + return { + storeId: store.id, + regionId: store.region_id, + name: store.name, + address: store.address, + }; +}; \ No newline at end of file diff --git a/src/modules/stores/repositories/store.repository.ts b/src/modules/stores/repositories/store.repository.ts new file mode 100644 index 0000000..5b30705 --- /dev/null +++ b/src/modules/stores/repositories/store.repository.ts @@ -0,0 +1,33 @@ +import { prisma } from "../../../db.config.js"; + +export const findRegionById = async (regionId: number) => { + return await prisma.region.findFirst({ + where: { + id: regionId, + }, + }); +}; + +export const addStore = async (data: { + regionId: number; + name: string; + address: string; +}) => { + const createdStore = await prisma.store.create({ + data: { + regionId: data.regionId, + name: data.name, + address: data.address, + }, + }); + + return createdStore.id; +}; + +export const findStoreById = async (storeId: number) => { + return await prisma.store.findFirst({ + where: { + id: storeId, + }, + }); +}; \ No newline at end of file diff --git a/src/modules/stores/services/store.service.ts b/src/modules/stores/services/store.service.ts new file mode 100644 index 0000000..7087e8d --- /dev/null +++ b/src/modules/stores/services/store.service.ts @@ -0,0 +1,24 @@ +import { + addStore, + findRegionById, + findStoreById, +} from "../repositories/store.repository.js"; +import { responseFromStore } from "../dtos/store.dto.js"; + +export const createStore = async (data: { + regionId: number; + name: string; + address: string; +}) => { + const region = await findRegionById(data.regionId); + + if (!region) { + throw new Error("존재하지 않는 지역입니다."); + } + + const storeId = await addStore(data); + + const store = await findStoreById(storeId); + + return responseFromStore(store); +}; \ No newline at end of file diff --git a/src/modules/users/controllers/user.controller.ts b/src/modules/users/controllers/user.controller.ts new file mode 100644 index 0000000..2010fc8 --- /dev/null +++ b/src/modules/users/controllers/user.controller.ts @@ -0,0 +1,94 @@ +import { + Body, + Controller, + Get, + Middlewares, + Post, + Request, + Route, + Tags, +} from "tsoa"; +import { Request as ExpressRequest } from "express"; + +import { UserSignUpRequest, UserSignUpResponse } from "../dtos/user.dto.js"; +import { userSignUp } from "../services/user.service.js"; +import { ApiResponse, success } from "../../../common/responses/response.js"; +import { authorizeUser } from "../../../common/middlewares/auth.middleware.js"; + +@Route("users") +@Tags("Users") +export class UserController extends Controller { + @Post("signup") + public async handleUserSignUp( + @Body() body: UserSignUpRequest + ): Promise> { + console.log("회원가입을 요청했습니다!"); + console.log("body:", body); + + const user = await userSignUp(body); + + return success(user); + } + + @Get("guest") + public async handleGuestPage(): Promise { + return ` +

게스트 페이지

+

이 페이지는 로그인이 필요 없습니다.

+ + `; + } + + @Get("login") + public async handleLoginPage(): Promise { + return ` +

로그인 페이지

+

로그인이 필요한 페이지에서 이동합니다.

+ 로그인 쿠키 생성하기 + `; + } + + @Get("mypage") + @Middlewares(authorizeUser()) + public async handleMypage( + @Request() req: ExpressRequest + ): Promise { + return ` +

마이페이지

+

환영합니다, ${req.cookies.username}님!

+

이 페이지는 로그인한 사람만 볼 수 있습니다.

+ 로그아웃 + `; + } + + @Get("set-login") + public async handleSetLogin( + @Request() req: ExpressRequest + ): Promise { + req.res!.cookie("username", "UMC10th", { + maxAge: 3600000, + httpOnly: true, + }); + + return ` +

로그인 쿠키 생성 완료

+

username=UMC10th 쿠키가 생성되었습니다.

+ 마이페이지로 이동 + `; + } + + @Get("set-logout") + public async handleSetLogout( + @Request() req: ExpressRequest + ): Promise { + req.res!.clearCookie("username"); + + return ` +

로그아웃 완료

+

쿠키가 삭제되었습니다.

+ 게스트 페이지로 이동 + `; + } +} \ No newline at end of file diff --git a/src/modules/users/dtos/user.dto.ts b/src/modules/users/dtos/user.dto.ts new file mode 100644 index 0000000..6a75302 --- /dev/null +++ b/src/modules/users/dtos/user.dto.ts @@ -0,0 +1,20 @@ +export interface UserSignUpRequest { + email: string; + password?: string; + name: string; + gender: string; + + // JSON 요청에서는 날짜가 문자열로 들어오므로 string으로 받는 것이 안전합니다. + // Service에서 new Date(data.birth)로 Date 객체로 변환합니다. + birth: string; + + address?: string; + detailAddress?: string; + phoneNumber: string; + preferences: number[]; +} + +export interface UserSignUpResponse { + userId: number; + preferences: string[]; +} \ No newline at end of file diff --git a/src/modules/users/repositories/user.repository.ts b/src/modules/users/repositories/user.repository.ts new file mode 100644 index 0000000..1c77d44 --- /dev/null +++ b/src/modules/users/repositories/user.repository.ts @@ -0,0 +1,73 @@ +import { prisma } from "../../../db.config.js"; + +export const addUser = async (data: { + email: string; + password?: string; + name: string; + gender: string; + birth: Date; + address: string; + detailAddress: string; + phoneNumber: string; +}) => { + // 1. 이미 존재하는 이메일인지 확인 + const user = await prisma.user.findFirst({ + where: { + email: data.email, + }, + }); + + if (user) { + return null; + } + + // 2. 새로운 사용자 생성 + const createdUser = await prisma.user.create({ + data: { + email: data.email, + password: data.password, + name: data.name, + gender: data.gender, + birth: data.birth, + address: data.address, + detailAddress: data.detailAddress, + phoneNumber: data.phoneNumber, + }, + }); + + return createdUser.id; +}; + +export const setPreference = async ( + userId: number, + foodCategoryId: number +) => { + await prisma.userFavorCategory.create({ + data: { + userId, + foodCategoryId, + }, + }); +}; + +export const getUser = async (userId: number) => { + return await prisma.user.findFirstOrThrow({ + where: { + id: userId, + }, + }); +}; + +export const getUserPreferencesByUserId = async (userId: number) => { + return await prisma.userFavorCategory.findMany({ + where: { + userId, + }, + include: { + foodCategory: true, + }, + orderBy: { + foodCategoryId: "asc", + }, + }); +}; \ No newline at end of file diff --git a/src/modules/users/services/user.service.ts b/src/modules/users/services/user.service.ts new file mode 100644 index 0000000..0abd917 --- /dev/null +++ b/src/modules/users/services/user.service.ts @@ -0,0 +1,51 @@ +import { UserSignUpRequest, UserSignUpResponse } from "../dtos/user.dto.js"; +import { + addUser, + getUser, + getUserPreferencesByUserId, + setPreference, +} from "../repositories/user.repository.js"; +import { DuplicateUserEmailError } from "../../../common/errors/user.error.js"; + +export const userSignUp = async ( + data: UserSignUpRequest +): Promise => { + const joinUserId = await addUser({ + email: data.email, + password: data.password, + name: data.name, + gender: data.gender, + + // 요청 body에서는 birth가 문자열로 들어오기 때문에 Date 객체로 변환합니다. + birth: new Date(data.birth), + + // 선택값은 undefined로 들어올 수 있으므로 기본값을 넣어줍니다. + address: data.address ?? "", + detailAddress: data.detailAddress ?? "", + phoneNumber: data.phoneNumber, + }); + + // addUser에서 이메일 중복이면 null을 반환하도록 되어 있으므로, + // 이 경우 커스텀 에러를 던집니다. + if (joinUserId === null) { + throw new DuplicateUserEmailError(data); + } + + // 사용자가 선택한 음식 선호 카테고리를 저장합니다. + for (const preference of data.preferences) { + await setPreference(joinUserId, preference); + } + + // 생성된 사용자 정보를 다시 조회합니다. + const user = await getUser(joinUserId); + + // 사용자의 선호 카테고리 이름 목록을 조회합니다. + const preferences = (await getUserPreferencesByUserId(joinUserId)).map( + (obj) => obj.foodCategory.name + ); + + return { + userId: user.id, + preferences, + }; +}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index f8884ca..fc94250 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "libReplacement": true, /* Enable lib replacement. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ diff --git a/tsoa.json b/tsoa.json new file mode 100644 index 0000000..804be4b --- /dev/null +++ b/tsoa.json @@ -0,0 +1,12 @@ +{ + "entryFile": "src/index.ts", + "noImplicitAdditionalProperties": "throw-on-extras", + "controllerPathGlobs": ["src/**/*.controller.ts"], + "spec": { + "outputDirectory": "dist", + "specVersion": 3 + }, + "routes": { + "routesDir": "src/generated" + } +} \ No newline at end of file From d4366e70123e4e47f44e81460ab3e44f9b9feb12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=B0=AC=ED=98=81?= Date: Thu, 28 May 2026 00:41:24 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=209=EC=A3=BC=EC=B0=A8=20Google=20OAut?= =?UTF-8?q?h=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=A4=EC=8A=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- misson_practice/chapter09/README.md | 634 ++++++++++++++++++ .../images/01-google-oauth-client-setting.png | Bin 0 -> 64530 bytes .../chapter09/images/02-env-setting.png | Bin 0 -> 27211 bytes .../chapter09/images/03-server-running.png | Bin 0 -> 4469 bytes .../chapter09/images/04-google-login-page.png | Bin 0 -> 15287 bytes .../images/05-google-consent-page.png | Bin 0 -> 64291 bytes .../images/06-google-login-token-response.png | Bin 0 -> 13914 bytes .../images/07-mypage-with-bearer-token.png | Bin 0 -> 28134 bytes .../images/08-mypage-without-token.png | Bin 0 -> 4278 bytes package-lock.json | 287 +++++++- package.json | 8 + src/auth.config.ts | 141 ++++ src/index.ts | 84 +++ 13 files changed, 1153 insertions(+), 1 deletion(-) create mode 100644 misson_practice/chapter09/README.md create mode 100644 misson_practice/chapter09/images/01-google-oauth-client-setting.png create mode 100644 misson_practice/chapter09/images/02-env-setting.png create mode 100644 misson_practice/chapter09/images/03-server-running.png create mode 100644 misson_practice/chapter09/images/04-google-login-page.png create mode 100644 misson_practice/chapter09/images/05-google-consent-page.png create mode 100644 misson_practice/chapter09/images/06-google-login-token-response.png create mode 100644 misson_practice/chapter09/images/07-mypage-with-bearer-token.png create mode 100644 misson_practice/chapter09/images/08-mypage-without-token.png create mode 100644 src/auth.config.ts diff --git a/misson_practice/chapter09/README.md b/misson_practice/chapter09/README.md new file mode 100644 index 0000000..4310d3e --- /dev/null +++ b/misson_practice/chapter09/README.md @@ -0,0 +1,634 @@ +좋아. 이제 **9주차 실습**으로 넘어가자. +이번 실습은 `10th_Node.js_Practice_Mission` 레포에서 진행하면 되고, 핵심은 **Google OAuth 로그인 구현 → JWT 발급 → Bearer Token으로 보호 라우트 테스트**야. 워크북도 Google 로그인 구현과 JWT 인증 흐름을 실습 목표로 잡고 있어. + +--- + +# 9주차 실습 진행 순서 + +## 0. 실습 목표 + +이번 실습에서 최종적으로 확인해야 하는 것은 아래 3가지야. + +```text +1. /oauth2/login/google 접속 시 Google 로그인 화면으로 이동 +2. Google 로그인 성공 후 accessToken, refreshToken 발급 +3. 발급받은 accessToken을 Bearer Token으로 사용해 보호 라우트 접근 +``` + +실습 결과물은 `misson_practice/chapter09` 또는 기존 레포 구조에 맞는 실습 폴더에 정리하면 돼. +너희 레포가 이전 주차에서 `misson_practice`를 쓰고 있었으면 그 흐름을 따르는 게 좋아. + +--- + +# 1. 브랜치 생성 + +```bash +cd /c/Project/10th_Node.js_Practice_Mission +git status +git switch main +git pull origin main +git switch -c feature/chapter-09 +``` + +실습 폴더 생성: + +```bash +mkdir -p misson_practice/chapter09/images +touch misson_practice/chapter09/README.md +``` + +스크린샷은 여기부터 찍을 필요는 없고, 실제 Google 설정/실행 결과 위주로 찍으면 돼. + +--- + +# 2. 패키지 설치 + +9주차 실습에서 필요한 인증 관련 패키지를 설치해. + +```bash +npm install passport passport-google-oauth20 jsonwebtoken passport-jwt +npm install --save-dev @types/passport @types/passport-google-oauth20 @types/jsonwebtoken @types/passport-jwt +``` + +각 패키지 역할은 다음과 같아. + +| 패키지 | 역할 | +| ------------------------- | ------------------- | +| `passport` | 인증 처리 프레임워크 | +| `passport-google-oauth20` | Google OAuth 로그인 전략 | +| `jsonwebtoken` | JWT 생성 | +| `passport-jwt` | JWT 검증 | +| `@types/...` | TypeScript 타입 지원 | + +설치 후 확인: + +```bash +cat package.json +``` + +--- + +# 3. Google Cloud OAuth Client 생성 + +이번 실습에서 가장 중요한 설정 단계야. + +## 3-1. Google Cloud Console 접속 + +Google Cloud Console에서 프로젝트를 생성하거나 기존 프로젝트를 선택해. + +이후 이동: + +```text +APIs & Services → Credentials +``` + +그리고: + +```text +Create Credentials → OAuth client ID +``` + +## 3-2. OAuth Client 설정 + +Application type은 보통 아래로 선택해. + +```text +Web application +``` + +Authorized JavaScript origins: + +```text +http://localhost:3000 +``` + +Authorized redirect URIs: + +```text +http://localhost:3000/oauth2/callback/google +``` + +중요한 건 이 주소가 코드의 `callbackURL`과 정확히 일치해야 한다는 점이야. + +```ts +callbackURL: "/oauth2/callback/google" +``` + +서버가 `localhost:3000`에서 실행되므로 Google Cloud에는 전체 주소로 등록해야 해. + +```text +http://localhost:3000/oauth2/callback/google +``` + +## 스크린샷 1 +![OAuth Client 설정](./images/01-google-oauth-client-setting.png) + +--- + +# 4. `.env` 설정 + +프로젝트 루트의 `.env`에 아래 값을 추가해. + +```env +PORT=3000 +DATABASE_URL="mysql://root:비밀번호@127.0.0.1:3306/umc_week9" + +PASSPORT_GOOGLE_CLIENT_ID="발급받은 Client ID" +PASSPORT_GOOGLE_CLIENT_SECRET="발급받은 Client Secret" + +JWT_SECRET="my-very-secret-jwt-key" +``` + +주의할 점: + +```text +.env는 절대 GitHub에 올리면 안 됨 +Client Secret은 스크린샷에서도 가려야 함 +JWT_SECRET은 임의의 긴 문자열로 설정 +``` + +## 스크린샷 2 +![.env 설정 화면](images/02-env-setting.png) + +--- + +# 5. `src/auth.config.ts` 생성 + +이제 인증 설정 파일을 만들자. + +```bash +touch src/auth.config.ts +``` + +아래 코드를 넣어줘. + +```ts +import dotenv from "dotenv"; +import { Strategy as GoogleStrategy, Profile } from "passport-google-oauth20"; +import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt"; +import jwt from "jsonwebtoken"; +import { prisma } from "./db.config.js"; + +dotenv.config(); + +export const generateAccessToken = (user: { id: number; email: string }) => { + return jwt.sign( + { + id: user.id, + email: user.email, + }, + process.env.JWT_SECRET!, + { + expiresIn: "1h", + } + ); +}; + +export const generateRefreshToken = (user: { id: number }) => { + return jwt.sign( + { + id: user.id, + }, + process.env.JWT_SECRET!, + { + expiresIn: "14d", + } + ); +}; + +const googleVerify = async (profile: Profile) => { + const email = profile.emails?.[0]?.value; + + if (!email) { + throw new Error("Google 프로필에 이메일이 없습니다."); + } + + let user = await prisma.user.findFirst({ + where: { email }, + }); + + if (!user) { + user = await prisma.user.create({ + data: { + email, + name: profile.displayName, + gender: "추후 수정", + birth: new Date(1970, 0, 1), + address: "추후 수정", + detailAddress: "추후 수정", + phoneNumber: "추후 수정", + }, + }); + } + + return { + id: user.id, + email: user.email, + name: user.name, + }; +}; + +export const googleStrategy = new GoogleStrategy( + { + clientID: process.env.PASSPORT_GOOGLE_CLIENT_ID!, + clientSecret: process.env.PASSPORT_GOOGLE_CLIENT_SECRET!, + callbackURL: "/oauth2/callback/google", + scope: ["email", "profile"], + }, + async (_accessToken, _refreshToken, profile, cb) => { + try { + const user = await googleVerify(profile); + + const tokens = { + accessToken: generateAccessToken(user), + refreshToken: generateRefreshToken(user), + }; + + return cb(null, tokens); + } catch (err) { + return cb(err as Error); + } + } +); + +export const jwtStrategy = new JwtStrategy( + { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: process.env.JWT_SECRET!, + }, + async (payload, done) => { + try { + const user = await prisma.user.findFirst({ + where: { id: payload.id }, + }); + + if (!user) { + return done(null, false); + } + + return done(null, user); + } catch (err) { + return done(err, false); + } + } +); +``` + +주의할 부분은 `prisma` import 경로야. + +```ts +import { prisma } from "./db.config.js"; +``` + +만약 네 프로젝트에서 `db.config.ts`의 export 이름이 다르면 여기를 맞춰야 해. + +--- + +# 6. `src/index.ts`에 Passport 등록 + +`src/index.ts` 상단에 import 추가: + +```ts +import passport from "passport"; +import { googleStrategy, jwtStrategy } from "./auth.config.js"; +``` + +`app` 생성 전후 흐름에 아래 코드 추가: + +```ts +passport.use(googleStrategy); +passport.use(jwtStrategy); +``` + +미들웨어 부분에 추가: + +```ts +app.use(passport.initialize()); +``` + +전체 흐름은 대략 이렇게 돼. + +```ts +passport.use(googleStrategy); +passport.use(jwtStrategy); + +const app = express(); + +app.use(cors()); +app.use(express.static("public")); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); + +app.use(passport.initialize()); +``` + +--- + +# 7. Google 로그인 라우트 추가 + +`src/index.ts`에 아래 라우트를 추가해. + +```ts +app.get( + "/oauth2/login/google", + passport.authenticate("google", { session: false }) +); + +app.get( + "/oauth2/callback/google", + passport.authenticate("google", { + session: false, + failureRedirect: "/login-failed", + }), + (req, res) => { + res.status(200).json({ + success: true, + tokens: req.user, + }); + } +); + +app.get("/login-failed", (req, res) => { + res.status(401).json({ + success: false, + message: "Google 로그인에 실패했습니다.", + }); +}); +``` + +이제 브라우저에서 아래 주소로 접속하면 Google 로그인 화면으로 이동해야 해. + +```text +http://localhost:3000/oauth2/login/google +``` + +--- + +# 8. 보호 라우트 만들기 + +JWT 인증이 되는지 확인하기 위해 `/mypage` 테스트 라우트를 추가해. + +```ts +const isLogin = passport.authenticate("jwt", { session: false }); + +app.get("/mypage", isLogin, (req, res) => { + res.status(200).json({ + success: true, + message: "인증 성공! 마이페이지에 접근했습니다.", + user: req.user, + }); +}); +``` + +이 라우트는 Access Token이 있어야 접근할 수 있어. + +--- + +# 9. 서버 실행 + +```bash +npm run dev +``` + +정상 실행 로그: + +```text +[server]: Server is running at http://localhost:3000 +``` + +## 스크린샷 3 +![npm run dev 서버 정상 실행 화면](images/03-server-running.png) + + +--- + +# 10. Google 로그인 테스트 + +브라우저에서 접속: +```text +http://localhost:3000/oauth2/login/google +``` + +정상이라면 Google 로그인 화면으로 이동해. + +## 스크린샷 4 +![Google 로그인 화면](images/04-google-login-page.png) + +로그인 후 권한 동의 화면이 나오면 확인해. + +## 스크린샷 5 +![Google 권한 동의 화면](images/05-google-consent-page.png) + +로그인이 성공하면 callback으로 돌아오고, 브라우저에 JSON 형태로 토큰이 보여야 해. + +예시: + +```json +{ + "success": true, + "tokens": { + "accessToken": "...", + "refreshToken": "..." + } +} +``` + +## 스크린샷 6 +![accessToken, refreshToken 응답 화면](images/06-google-login-token-response.png) + +--- + +# 11. Postman에서 Bearer Token 테스트 + +발급받은 `accessToken`을 복사해. + +Postman에서 새 요청 생성: + +```text +GET http://localhost:3000/mypage +``` + +Headers에 추가: + +```text +Authorization: Bearer +``` + +정상 응답: + +```json +{ + "success": true, + "message": "인증 성공! 마이페이지에 접근했습니다.", + "user": { + ... + } +} +``` + +## 스크린샷 7 + +![Postman에서 Bearer Token을 넣고 /mypage 성공한 화면](images/07-mypage-with-bearer-token.png) + + +--- + +# 12. 토큰 없이 접근 테스트 + +이번에는 Authorization 헤더를 제거하고 다시 요청해. + +```text +GET http://localhost:3000/mypage +``` + +정상이라면 인증 실패가 떠야 해. + +```text +Unauthorized +``` + +또는 401 응답이 나와야 해. + +## 스크린샷 8 +![토큰 없이 /mypage 요청 시 실패 화면](images/08-mypage-without-token.png) + +--- + +# 13. 실습 README에 넣을 내용 + +`misson_practice/chapter09/README.md`에는 다음 구조로 작성하면 돼. + +````md +# 9주차 실습 README + +## 1. 실습 목표 + +9주차 실습의 목표는 Google OAuth 로그인을 구현하고, 로그인 성공 후 JWT를 발급받아 Bearer Token 방식으로 보호된 API에 접근하는 것이다. + +## 2. 설치한 패키지 + +```bash +npm install passport passport-google-oauth20 jsonwebtoken passport-jwt +npm install --save-dev @types/passport @types/passport-google-oauth20 @types/jsonwebtoken @types/passport-jwt +```` + +## 3. Google OAuth Client 설정 + +Google Cloud Console에서 OAuth Client ID를 발급받고, Redirect URI를 다음과 같이 설정하였다. + +```text +http://localhost:3000/oauth2/callback/google +``` + +![Google OAuth 설정](./images/01-google-oauth-client-setting.png) + +## 4. 환경변수 설정 + +`.env`에 Google Client ID, Client Secret, JWT Secret을 추가하였다. + +![환경변수 설정](./images/02-env-setting.png) + +## 5. Google 로그인 구현 + +`src/auth.config.ts`에서 Google Strategy와 JWT 생성 함수를 작성하였다. + +## 6. 로그인 라우트 추가 + +```ts +app.get( + "/oauth2/login/google", + passport.authenticate("google", { session: false }) +); +``` + +## 7. 로그인 성공 후 토큰 발급 확인 + +Google 로그인 성공 후 accessToken과 refreshToken이 응답되는 것을 확인하였다. + +![토큰 응답](./images/06-google-login-token-response.png) + +## 8. 보호 라우트 테스트 + +`/mypage` 라우트에 JWT 인증 미들웨어를 적용하였다. + +```ts +const isLogin = passport.authenticate("jwt", { session: false }); +``` + +Bearer Token을 넣고 요청했을 때는 성공하였다. + +![Bearer Token 성공](./images/07-mypage-with-bearer-token.png) + +토큰 없이 요청했을 때는 인증에 실패하였다. + +![토큰 없이 실패](./images/08-mypage-without-token.png) + +## 9. 느낀 점 + +이번 실습을 통해 Google OAuth 로그인은 외부 서비스에서 사용자를 인증받는 과정이고, 이후 우리 서버가 자체 JWT를 발급해야 우리 서비스의 API 인증에 사용할 수 있다는 점을 이해할 수 있었다. + +또한 JWT를 발급받는 것만으로는 인증이 끝나는 것이 아니라, API 요청 시 Authorization 헤더에 Bearer Token 형식으로 토큰을 담아 보내야 보호된 API를 사용할 수 있다는 점을 확인하였다. + +```` + +--- + +# 14. 실습 커밋 + +실습 완료 후 커밋: + +```bash +git status +git add package.json package-lock.json +git add src/auth.config.ts src/index.ts +git add misson_practice/chapter09/README.md misson_practice/chapter09/images/ +git commit -m "feat: 9주차 Google OAuth 로그인 실습" +git push -u origin feature/chapter-09 +```` + +--- + +# 15. 시니어 미션 판단 + +이번 주 공통 미션만 해도 구현량이 많아. + +공통 미션 핵심: + +```text +1. 하드코딩된 사용자 정보 제거 +2. 내 정보 수정 API 또는 기존 회원가입 API 수정 +3. 기존 API에 JWT 인증 미들웨어 적용 +``` + +시니어 미션 중 추가로 할 만한 것은 이론 정리 정도야. + +추천: + +```text +세션 기반 인증과 JWT 인증 비교 정리 +``` + +비추천: + +```text +다른 소셜 로그인 추가 +이메일/비밀번호 로그인 추가 +가입 방식 제한 로직 구현 +``` + +이유는 DB 모델 변경과 인증 전략 추가가 필요해서 이번 주 공통 미션과 같이 하기에는 범위가 꽤 커. + +--- + +## 다음 단계 + +이제 실제 실습을 시작할 때는 이 순서로 가면 돼. + +```bash +cd /c/Project/10th_Node.js_Practice_Mission +git switch main +git pull origin main +git switch -c feature/chapter-09 +``` + +그다음 패키지 설치부터 시작하면 돼. diff --git a/misson_practice/chapter09/images/01-google-oauth-client-setting.png b/misson_practice/chapter09/images/01-google-oauth-client-setting.png new file mode 100644 index 0000000000000000000000000000000000000000..014b5a9418451d330fae07ef6a0d457a104d025f GIT binary patch literal 64530 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 BHzxo9 literal 0 HcmV?d00001 diff --git a/misson_practice/chapter09/images/02-env-setting.png b/misson_practice/chapter09/images/02-env-setting.png new file mode 100644 index 0000000000000000000000000000000000000000..5fc450f31fac582ef9dd6a7c9be38a8baf533d04 GIT binary patch literal 27211 zcmd43byQW~`!1>|q9C9kAt@cwv1yP7Vbduo-L>gfkd$sVjnaa2hf;!ccZYO$-MRgI zzUSU^@BNK)?zn%PGlmS<3)Wh5&H1i(KJW89ufYm(lIW-esQ2#OLzk8kQ@VE#3FqFu z`*@EZfg=QMRe!*@`!FR*k$c5`L>u74LsMa>@V$Gb5op)%k-_IDc2b(Kd-pI}5nuN^ zZS#!o-7{a1786!+)!mtO^Hdp~JwNF0F-kI$V(T)ZK%0Dk|1sb(QkfZ@**A@SIwv~o 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 Iim#&n3yY_+*#H0l literal 0 HcmV?d00001 diff --git a/misson_practice/chapter09/images/04-google-login-page.png b/misson_practice/chapter09/images/04-google-login-page.png new file mode 100644 index 0000000000000000000000000000000000000000..aaddd6f32fc1fa23c1cc6704291729fce9fc01d8 GIT binary patch literal 15287 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 literal 0 HcmV?d00001 diff --git a/misson_practice/chapter09/images/05-google-consent-page.png b/misson_practice/chapter09/images/05-google-consent-page.png new file mode 100644 index 0000000000000000000000000000000000000000..82d429c128f507ce5adc6ae39d6f1365f2850273 GIT binary patch literal 64291 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 V1CJKO4{revSt%vSvUf%S{|i+N7V-c9 literal 0 HcmV?d00001 diff --git a/misson_practice/chapter09/images/06-google-login-token-response.png b/misson_practice/chapter09/images/06-google-login-token-response.png new file mode 100644 index 0000000000000000000000000000000000000000..1228173e933c1dceef4a21ab303aa50a8b06636b GIT binary patch literal 13914 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 literal 0 HcmV?d00001 diff --git a/misson_practice/chapter09/images/07-mypage-with-bearer-token.png b/misson_practice/chapter09/images/07-mypage-with-bearer-token.png new file mode 100644 index 0000000000000000000000000000000000000000..3b44c113e30d96de182c4783ca4f45d918c50835 GIT binary patch literal 28134 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 z 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 IQt$l#4fWQE>i_@% literal 0 HcmV?d00001 diff --git a/misson_practice/chapter09/images/08-mypage-without-token.png b/misson_practice/chapter09/images/08-mypage-without-token.png new file mode 100644 index 0000000000000000000000000000000000000000..a88d3c8b5224201a7fb0c3adafff0e8174cdb522 GIT binary patch literal 4278 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" } }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -1980,6 +2081,12 @@ "node": ">=8" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2426,6 +2533,15 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -3222,6 +3338,91 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -3541,6 +3742,12 @@ "node": ">=0.10.0" } }, + "node_modules/oauth": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", + "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3614,6 +3821,74 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-google-oauth20": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", + "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "license": "MIT", + "dependencies": { + "passport-oauth2": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "license": "MIT", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "license": "MIT", + "dependencies": { + "base64url": "3.x.x", + "oauth": "0.10.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3656,6 +3931,11 @@ "devOptional": true, "license": "MIT" }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/perfect-debounce": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", @@ -3975,7 +4255,6 @@ "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4421,6 +4700,12 @@ "node": ">=0.8.0" } }, + "node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==", + "license": "MIT" + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", diff --git a/package.json b/package.json index 3350a68..36ec866 100644 --- a/package.json +++ b/package.json @@ -26,13 +26,21 @@ "cors": "^2.8.6", "dotenv": "^17.4.2", "express": "^5.2.1", + "jsonwebtoken": "^9.0.3", "morgan": "^1.10.1", + "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", + "passport-jwt": "^4.0.1", "tsoa": "^7.0.0-alpha.0" }, "devDependencies": { "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", + "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", + "@types/passport": "^1.0.17", + "@types/passport-google-oauth20": "^2.0.17", + "@types/passport-jwt": "^4.0.1", "nodemon": "^3.1.14", "prisma": "^7.8.0", "tsx": "^4.21.0" diff --git a/src/auth.config.ts b/src/auth.config.ts new file mode 100644 index 0000000..0d08f09 --- /dev/null +++ b/src/auth.config.ts @@ -0,0 +1,141 @@ +import dotenv from "dotenv"; +import { Strategy as GoogleStrategy, Profile } from "passport-google-oauth20"; +import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt"; +import jwt from "jsonwebtoken"; +import { prisma } from "./db.config.js"; + +dotenv.config(); + +/** + * Access Token 생성 + * + * Access Token은 일반 API 요청에 사용하는 짧은 수명의 토큰입니다. + * Payload에는 사용자를 식별할 수 있는 최소 정보만 담습니다. + */ +export const generateAccessToken = (user: { id: number; email: string }) => { + return jwt.sign( + { + id: user.id, + email: user.email, + }, + process.env.JWT_SECRET!, + { + expiresIn: "1h", + } + ); +}; + +/** + * Refresh Token 생성 + * + * Refresh Token은 Access Token을 재발급받기 위한 긴 수명의 토큰입니다. + * 여기서는 최소 정보인 user id만 담습니다. + */ +export const generateRefreshToken = (user: { id: number }) => { + return jwt.sign( + { + id: user.id, + }, + process.env.JWT_SECRET!, + { + expiresIn: "14d", + } + ); +}; + +/** + * Google 로그인 후 사용자 조회 또는 생성 + * + * Google에서 전달받은 profile 정보 중 이메일을 기준으로 + * 기존 사용자가 있으면 조회하고, 없으면 새로 생성합니다. + */ +const googleVerify = async (profile: Profile) => { + const email = profile.emails?.[0]?.value; + + if (!email) { + throw new Error("Google 프로필에 이메일이 없습니다."); + } + + let user = await prisma.user.findFirst({ + where: { email }, + }); + + if (!user) { + user = await prisma.user.create({ + data: { + email, + name: profile.displayName, + gender: "추후 수정", + birth: new Date(1970, 0, 1), + address: "추후 수정", + detailAddress: "추후 수정", + phoneNumber: "추후 수정", + }, + }); + } + + return { + id: user.id, + email: user.email, + name: user.name, + }; +}; + +/** + * Google OAuth Strategy + * + * /oauth2/login/google 로 접속하면 Google 로그인 화면으로 이동하고, + * 로그인 성공 후 /oauth2/callback/google 로 돌아옵니다. + */ +export const googleStrategy = new GoogleStrategy( + { + clientID: process.env.PASSPORT_GOOGLE_CLIENT_ID!, + clientSecret: process.env.PASSPORT_GOOGLE_CLIENT_SECRET!, + callbackURL: "http://localhost:3000/oauth2/callback/google", + scope: ["email", "profile"], + }, + async (_accessToken, _refreshToken, profile, cb) => { + try { + const user = await googleVerify(profile); + + const tokens = { + accessToken: generateAccessToken(user), + refreshToken: generateRefreshToken(user), + }; + + return cb(null, tokens); + } catch (err) { + return cb(err as Error); + } + } +); + +/** + * JWT Strategy + * + * Authorization 헤더에 담긴 Bearer Token을 검증합니다. + * 토큰이 유효하면 req.user에 사용자 정보를 넣어줍니다. + */ +export const jwtStrategy = new JwtStrategy( + { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: process.env.JWT_SECRET!, + }, + async (payload: { id: number; email?: string }, done) => { + try { + const user = await prisma.user.findFirst({ + where: { + id: payload.id, + }, + }); + + if (!user) { + return done(null, false); + } + + return done(null, user); + } catch (err) { + return done(err, false); + } + } +); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index e984fbb..eb44365 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,12 +3,23 @@ import express, { Express, NextFunction, Request, Response } from "express"; import cors from "cors"; import morgan from "morgan"; import cookieParser from "cookie-parser"; +import passport from "passport"; import { RegisterRoutes } from "./generated/routes.js"; import { AppError } from "./common/errors/app.error.js"; +import { googleStrategy, jwtStrategy } from "./auth.config.js"; dotenv.config(); +/** + * Passport Strategy 등록 + * + * googleStrategy: Google OAuth 로그인 처리 + * jwtStrategy: Bearer Token 기반 JWT 인증 처리 + */ +passport.use(googleStrategy); +passport.use(jwtStrategy); + const app: Express = express(); const port = process.env.PORT || 3000; @@ -22,6 +33,14 @@ app.use(express.static("public")); app.use(express.json()); app.use(express.urlencoded({ extended: false })); +/** + * Passport 초기화 + * + * session: false 방식으로 사용할 예정이기 때문에 + * express-session은 따로 사용하지 않습니다. + */ +app.use(passport.initialize()); + /** * 기본 서버 확인용 API */ @@ -29,8 +48,73 @@ app.get("/", (req: Request, res: Response) => { res.send("Hello World! This is TypeScript Server!"); }); +/** + * Google 로그인 시작 라우트 + * + * 브라우저에서 아래 주소로 접속하면 Google 로그인 화면으로 이동합니다. + * http://localhost:3000/oauth2/login/google + */ +app.get( + "/oauth2/login/google", + passport.authenticate("google", { session: false }) +); + +/** + * Google 로그인 Callback 라우트 + * + * Google 로그인이 성공하면 이 주소로 돌아옵니다. + * 성공 시 accessToken, refreshToken을 JSON으로 응답합니다. + */ +app.get( + "/oauth2/callback/google", + passport.authenticate("google", { + session: false, + failureRedirect: "/login-failed", + }), + (req: Request, res: Response) => { + res.status(200).json({ + success: true, + tokens: req.user, + }); + } +); + +/** + * Google 로그인 실패 라우트 + */ +app.get("/login-failed", (req: Request, res: Response) => { + res.status(401).json({ + success: false, + message: "Google 로그인에 실패했습니다.", + }); +}); + +/** + * JWT 인증 미들웨어 + * + * Authorization: Bearer + * 형식으로 토큰을 보내야 통과할 수 있습니다. + */ +const isLogin = passport.authenticate("jwt", { session: false }); + +/** + * JWT 인증 테스트용 보호 라우트 + * + * accessToken이 있으면 성공하고, + * 없거나 잘못된 토큰이면 401 Unauthorized가 발생합니다. + */ +app.get("/mypage", isLogin, (req: Request, res: Response) => { + res.status(200).json({ + success: true, + message: "인증 성공! 마이페이지에 접근했습니다.", + user: req.user, + }); +}); + /** * Tsoa가 자동 생성한 routes 등록 + * + * 기존 API들은 /api/v1 경로 아래에서 동작합니다. */ const router = express.Router(); From 126ba2f3b9d8a76aa4598645b046ae390f408b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=B0=AC=ED=98=81?= Date: Thu, 28 May 2026 00:50:43 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat:=209=EC=A3=BC=EC=B0=A8=20Google=20OAut?= =?UTF-8?q?h=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=A4=EC=8A=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- misson_practice/chapter09/README.md | 567 +--------------------------- 1 file changed, 1 insertion(+), 566 deletions(-) diff --git a/misson_practice/chapter09/README.md b/misson_practice/chapter09/README.md index 4310d3e..f2cdfd0 100644 --- a/misson_practice/chapter09/README.md +++ b/misson_practice/chapter09/README.md @@ -1,506 +1,3 @@ -좋아. 이제 **9주차 실습**으로 넘어가자. -이번 실습은 `10th_Node.js_Practice_Mission` 레포에서 진행하면 되고, 핵심은 **Google OAuth 로그인 구현 → JWT 발급 → Bearer Token으로 보호 라우트 테스트**야. 워크북도 Google 로그인 구현과 JWT 인증 흐름을 실습 목표로 잡고 있어. - ---- - -# 9주차 실습 진행 순서 - -## 0. 실습 목표 - -이번 실습에서 최종적으로 확인해야 하는 것은 아래 3가지야. - -```text -1. /oauth2/login/google 접속 시 Google 로그인 화면으로 이동 -2. Google 로그인 성공 후 accessToken, refreshToken 발급 -3. 발급받은 accessToken을 Bearer Token으로 사용해 보호 라우트 접근 -``` - -실습 결과물은 `misson_practice/chapter09` 또는 기존 레포 구조에 맞는 실습 폴더에 정리하면 돼. -너희 레포가 이전 주차에서 `misson_practice`를 쓰고 있었으면 그 흐름을 따르는 게 좋아. - ---- - -# 1. 브랜치 생성 - -```bash -cd /c/Project/10th_Node.js_Practice_Mission -git status -git switch main -git pull origin main -git switch -c feature/chapter-09 -``` - -실습 폴더 생성: - -```bash -mkdir -p misson_practice/chapter09/images -touch misson_practice/chapter09/README.md -``` - -스크린샷은 여기부터 찍을 필요는 없고, 실제 Google 설정/실행 결과 위주로 찍으면 돼. - ---- - -# 2. 패키지 설치 - -9주차 실습에서 필요한 인증 관련 패키지를 설치해. - -```bash -npm install passport passport-google-oauth20 jsonwebtoken passport-jwt -npm install --save-dev @types/passport @types/passport-google-oauth20 @types/jsonwebtoken @types/passport-jwt -``` - -각 패키지 역할은 다음과 같아. - -| 패키지 | 역할 | -| ------------------------- | ------------------- | -| `passport` | 인증 처리 프레임워크 | -| `passport-google-oauth20` | Google OAuth 로그인 전략 | -| `jsonwebtoken` | JWT 생성 | -| `passport-jwt` | JWT 검증 | -| `@types/...` | TypeScript 타입 지원 | - -설치 후 확인: - -```bash -cat package.json -``` - ---- - -# 3. Google Cloud OAuth Client 생성 - -이번 실습에서 가장 중요한 설정 단계야. - -## 3-1. Google Cloud Console 접속 - -Google Cloud Console에서 프로젝트를 생성하거나 기존 프로젝트를 선택해. - -이후 이동: - -```text -APIs & Services → Credentials -``` - -그리고: - -```text -Create Credentials → OAuth client ID -``` - -## 3-2. OAuth Client 설정 - -Application type은 보통 아래로 선택해. - -```text -Web application -``` - -Authorized JavaScript origins: - -```text -http://localhost:3000 -``` - -Authorized redirect URIs: - -```text -http://localhost:3000/oauth2/callback/google -``` - -중요한 건 이 주소가 코드의 `callbackURL`과 정확히 일치해야 한다는 점이야. - -```ts -callbackURL: "/oauth2/callback/google" -``` - -서버가 `localhost:3000`에서 실행되므로 Google Cloud에는 전체 주소로 등록해야 해. - -```text -http://localhost:3000/oauth2/callback/google -``` - -## 스크린샷 1 -![OAuth Client 설정](./images/01-google-oauth-client-setting.png) - ---- - -# 4. `.env` 설정 - -프로젝트 루트의 `.env`에 아래 값을 추가해. - -```env -PORT=3000 -DATABASE_URL="mysql://root:비밀번호@127.0.0.1:3306/umc_week9" - -PASSPORT_GOOGLE_CLIENT_ID="발급받은 Client ID" -PASSPORT_GOOGLE_CLIENT_SECRET="발급받은 Client Secret" - -JWT_SECRET="my-very-secret-jwt-key" -``` - -주의할 점: - -```text -.env는 절대 GitHub에 올리면 안 됨 -Client Secret은 스크린샷에서도 가려야 함 -JWT_SECRET은 임의의 긴 문자열로 설정 -``` - -## 스크린샷 2 -![.env 설정 화면](images/02-env-setting.png) - ---- - -# 5. `src/auth.config.ts` 생성 - -이제 인증 설정 파일을 만들자. - -```bash -touch src/auth.config.ts -``` - -아래 코드를 넣어줘. - -```ts -import dotenv from "dotenv"; -import { Strategy as GoogleStrategy, Profile } from "passport-google-oauth20"; -import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt"; -import jwt from "jsonwebtoken"; -import { prisma } from "./db.config.js"; - -dotenv.config(); - -export const generateAccessToken = (user: { id: number; email: string }) => { - return jwt.sign( - { - id: user.id, - email: user.email, - }, - process.env.JWT_SECRET!, - { - expiresIn: "1h", - } - ); -}; - -export const generateRefreshToken = (user: { id: number }) => { - return jwt.sign( - { - id: user.id, - }, - process.env.JWT_SECRET!, - { - expiresIn: "14d", - } - ); -}; - -const googleVerify = async (profile: Profile) => { - const email = profile.emails?.[0]?.value; - - if (!email) { - throw new Error("Google 프로필에 이메일이 없습니다."); - } - - let user = await prisma.user.findFirst({ - where: { email }, - }); - - if (!user) { - user = await prisma.user.create({ - data: { - email, - name: profile.displayName, - gender: "추후 수정", - birth: new Date(1970, 0, 1), - address: "추후 수정", - detailAddress: "추후 수정", - phoneNumber: "추후 수정", - }, - }); - } - - return { - id: user.id, - email: user.email, - name: user.name, - }; -}; - -export const googleStrategy = new GoogleStrategy( - { - clientID: process.env.PASSPORT_GOOGLE_CLIENT_ID!, - clientSecret: process.env.PASSPORT_GOOGLE_CLIENT_SECRET!, - callbackURL: "/oauth2/callback/google", - scope: ["email", "profile"], - }, - async (_accessToken, _refreshToken, profile, cb) => { - try { - const user = await googleVerify(profile); - - const tokens = { - accessToken: generateAccessToken(user), - refreshToken: generateRefreshToken(user), - }; - - return cb(null, tokens); - } catch (err) { - return cb(err as Error); - } - } -); - -export const jwtStrategy = new JwtStrategy( - { - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: process.env.JWT_SECRET!, - }, - async (payload, done) => { - try { - const user = await prisma.user.findFirst({ - where: { id: payload.id }, - }); - - if (!user) { - return done(null, false); - } - - return done(null, user); - } catch (err) { - return done(err, false); - } - } -); -``` - -주의할 부분은 `prisma` import 경로야. - -```ts -import { prisma } from "./db.config.js"; -``` - -만약 네 프로젝트에서 `db.config.ts`의 export 이름이 다르면 여기를 맞춰야 해. - ---- - -# 6. `src/index.ts`에 Passport 등록 - -`src/index.ts` 상단에 import 추가: - -```ts -import passport from "passport"; -import { googleStrategy, jwtStrategy } from "./auth.config.js"; -``` - -`app` 생성 전후 흐름에 아래 코드 추가: - -```ts -passport.use(googleStrategy); -passport.use(jwtStrategy); -``` - -미들웨어 부분에 추가: - -```ts -app.use(passport.initialize()); -``` - -전체 흐름은 대략 이렇게 돼. - -```ts -passport.use(googleStrategy); -passport.use(jwtStrategy); - -const app = express(); - -app.use(cors()); -app.use(express.static("public")); -app.use(express.json()); -app.use(express.urlencoded({ extended: false })); - -app.use(passport.initialize()); -``` - ---- - -# 7. Google 로그인 라우트 추가 - -`src/index.ts`에 아래 라우트를 추가해. - -```ts -app.get( - "/oauth2/login/google", - passport.authenticate("google", { session: false }) -); - -app.get( - "/oauth2/callback/google", - passport.authenticate("google", { - session: false, - failureRedirect: "/login-failed", - }), - (req, res) => { - res.status(200).json({ - success: true, - tokens: req.user, - }); - } -); - -app.get("/login-failed", (req, res) => { - res.status(401).json({ - success: false, - message: "Google 로그인에 실패했습니다.", - }); -}); -``` - -이제 브라우저에서 아래 주소로 접속하면 Google 로그인 화면으로 이동해야 해. - -```text -http://localhost:3000/oauth2/login/google -``` - ---- - -# 8. 보호 라우트 만들기 - -JWT 인증이 되는지 확인하기 위해 `/mypage` 테스트 라우트를 추가해. - -```ts -const isLogin = passport.authenticate("jwt", { session: false }); - -app.get("/mypage", isLogin, (req, res) => { - res.status(200).json({ - success: true, - message: "인증 성공! 마이페이지에 접근했습니다.", - user: req.user, - }); -}); -``` - -이 라우트는 Access Token이 있어야 접근할 수 있어. - ---- - -# 9. 서버 실행 - -```bash -npm run dev -``` - -정상 실행 로그: - -```text -[server]: Server is running at http://localhost:3000 -``` - -## 스크린샷 3 -![npm run dev 서버 정상 실행 화면](images/03-server-running.png) - - ---- - -# 10. Google 로그인 테스트 - -브라우저에서 접속: -```text -http://localhost:3000/oauth2/login/google -``` - -정상이라면 Google 로그인 화면으로 이동해. - -## 스크린샷 4 -![Google 로그인 화면](images/04-google-login-page.png) - -로그인 후 권한 동의 화면이 나오면 확인해. - -## 스크린샷 5 -![Google 권한 동의 화면](images/05-google-consent-page.png) - -로그인이 성공하면 callback으로 돌아오고, 브라우저에 JSON 형태로 토큰이 보여야 해. - -예시: - -```json -{ - "success": true, - "tokens": { - "accessToken": "...", - "refreshToken": "..." - } -} -``` - -## 스크린샷 6 -![accessToken, refreshToken 응답 화면](images/06-google-login-token-response.png) - ---- - -# 11. Postman에서 Bearer Token 테스트 - -발급받은 `accessToken`을 복사해. - -Postman에서 새 요청 생성: - -```text -GET http://localhost:3000/mypage -``` - -Headers에 추가: - -```text -Authorization: Bearer -``` - -정상 응답: - -```json -{ - "success": true, - "message": "인증 성공! 마이페이지에 접근했습니다.", - "user": { - ... - } -} -``` - -## 스크린샷 7 - -![Postman에서 Bearer Token을 넣고 /mypage 성공한 화면](images/07-mypage-with-bearer-token.png) - - ---- - -# 12. 토큰 없이 접근 테스트 - -이번에는 Authorization 헤더를 제거하고 다시 요청해. - -```text -GET http://localhost:3000/mypage -``` - -정상이라면 인증 실패가 떠야 해. - -```text -Unauthorized -``` - -또는 401 응답이 나와야 해. - -## 스크린샷 8 -![토큰 없이 /mypage 요청 시 실패 화면](images/08-mypage-without-token.png) - ---- - -# 13. 실습 README에 넣을 내용 - -`misson_practice/chapter09/README.md`에는 다음 구조로 작성하면 돼. - -````md -# 9주차 실습 README - ## 1. 실습 목표 9주차 실습의 목표는 Google OAuth 로그인을 구현하고, 로그인 성공 후 JWT를 발급받아 Bearer Token 방식으로 보호된 API에 접근하는 것이다. @@ -569,66 +66,4 @@ Bearer Token을 넣고 요청했을 때는 성공하였다. 또한 JWT를 발급받는 것만으로는 인증이 끝나는 것이 아니라, API 요청 시 Authorization 헤더에 Bearer Token 형식으로 토큰을 담아 보내야 보호된 API를 사용할 수 있다는 점을 확인하였다. -```` - ---- - -# 14. 실습 커밋 - -실습 완료 후 커밋: - -```bash -git status -git add package.json package-lock.json -git add src/auth.config.ts src/index.ts -git add misson_practice/chapter09/README.md misson_practice/chapter09/images/ -git commit -m "feat: 9주차 Google OAuth 로그인 실습" -git push -u origin feature/chapter-09 -```` - ---- - -# 15. 시니어 미션 판단 - -이번 주 공통 미션만 해도 구현량이 많아. - -공통 미션 핵심: - -```text -1. 하드코딩된 사용자 정보 제거 -2. 내 정보 수정 API 또는 기존 회원가입 API 수정 -3. 기존 API에 JWT 인증 미들웨어 적용 -``` - -시니어 미션 중 추가로 할 만한 것은 이론 정리 정도야. - -추천: - -```text -세션 기반 인증과 JWT 인증 비교 정리 -``` - -비추천: - -```text -다른 소셜 로그인 추가 -이메일/비밀번호 로그인 추가 -가입 방식 제한 로직 구현 -``` - -이유는 DB 모델 변경과 인증 전략 추가가 필요해서 이번 주 공통 미션과 같이 하기에는 범위가 꽤 커. - ---- - -## 다음 단계 - -이제 실제 실습을 시작할 때는 이 순서로 가면 돼. - -```bash -cd /c/Project/10th_Node.js_Practice_Mission -git switch main -git pull origin main -git switch -c feature/chapter-09 -``` - -그다음 패키지 설치부터 시작하면 돼. +```` \ No newline at end of file From 31b71eea5a9f6ad8d0323645dfb954561fbffe93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=B0=AC=ED=98=81?= Date: Thu, 18 Jun 2026 22:52:31 +0900 Subject: [PATCH 5/7] =?UTF-8?q?practice:=2010=EC=A3=BC=EC=B0=A8=20CI/CD=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 17 ++ .github/workflows/deploy-main.yml | 151 ++++++++++++++ .gitignore | 3 +- misson_practice/chapter10/README.md | 304 ++++++++++++++++++++++++++++ package-lock.json | 3 +- package.json | 10 +- src/auth.config.ts | 6 +- tsconfig.json | 6 +- 8 files changed, 489 insertions(+), 11 deletions(-) create mode 100644 .env.example create mode 100644 .github/workflows/deploy-main.yml create mode 100644 misson_practice/chapter10/README.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7acfe0c --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +PORT=3000 + +# MySQL connection used by Prisma +DATABASE_URL=mysql://root:change-me@localhost:3306/umc_10th +DB_HOST=localhost +DB_PORT=3306 +DB_USER=root +DB_PASSWORD=change-me +DB_NAME=umc_10th + +# Google OAuth +PASSPORT_GOOGLE_CLIENT_ID=replace-with-google-client-id +PASSPORT_GOOGLE_CLIENT_SECRET=replace-with-google-client-secret +PASSPORT_GOOGLE_CALLBACK_URL=http://localhost:3000/oauth2/callback/google + +# JWT signing secret +JWT_SECRET=replace-with-a-long-random-secret diff --git a/.github/workflows/deploy-main.yml b/.github/workflows/deploy-main.yml new file mode 100644 index 0000000..a1bdc0c --- /dev/null +++ b/.github/workflows/deploy-main.yml @@ -0,0 +1,151 @@ +name: deploy-main + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: deploy-main + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Create build environment + shell: bash + env: + EC2_DOT_ENV: ${{ secrets.EC2_DOT_ENV }} + run: printf '%s' "$EC2_DOT_ENV" > .env + + - name: Generate and build + run: npm run build + + - name: Keep production dependencies only + run: npm prune --omit=dev + + - name: Pack artifact + shell: bash + run: | + mkdir -p artifact + cp -r dist artifact/dist + cp -r node_modules artifact/node_modules + cp package.json package-lock.json artifact/ + tar -czf artifact.tgz -C artifact . + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: artifact.tgz + if-no-files-found: error + retention-days: 1 + + deploy: + runs-on: ubuntu-latest + needs: build + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: app-artifact + + - name: Extract artifact + run: | + mkdir -p artifact + tar -xzf artifact.tgz -C artifact + + - name: Configure SSH + shell: bash + env: + EC2_HOST: ${{ secrets.EC2_HOST }} + EC2_PORT: ${{ secrets.EC2_PORT }} + EC2_USER: ${{ secrets.EC2_USER }} + EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }} + run: | + install -m 700 -d ~/.ssh + printf '%s\n' "$EC2_SSH_KEY" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -p "$EC2_PORT" -H "$EC2_HOST" >> ~/.ssh/known_hosts + chmod 600 ~/.ssh/known_hosts + cat >> ~/.ssh/config < /opt/app/umc10th/incoming/.env && chmod 600 /opt/app/umc10th/incoming/.env' + + - name: Activate release + shell: bash + run: | + ssh target ' + set -eu + cd /opt/app/umc10th + rm -rf previous + if [ -d current ]; then mv current previous; fi + mv incoming current + ' + + - name: Configure and restart systemd service + shell: bash + run: | + cat <<'EOF' | ssh target 'sudo tee /etc/systemd/system/umc10th.service > /dev/null' + [Unit] + Description=UMC 10th Node.js API + After=network-online.target mysql.service + Wants=network-online.target + + [Service] + Type=simple + User=ubuntu + WorkingDirectory=/opt/app/umc10th/current + ExecStart=/bin/bash -lc 'source /home/ubuntu/.nvm/nvm.sh && exec npm start' + Restart=always + RestartSec=3 + Environment=NODE_ENV=production + + [Install] + WantedBy=multi-user.target + EOF + + ssh target 'sudo systemctl daemon-reload && sudo systemctl enable umc10th && sudo systemctl restart umc10th' + + - name: Verify deployment + shell: bash + run: | + ssh target 'sudo systemctl is-active --quiet umc10th' + ssh target 'curl --fail --silent --show-error --retry 5 --retry-delay 2 http://127.0.0.1:3000/' + ssh target 'sudo systemctl status umc10th --no-pager --lines=30' diff --git a/.gitignore b/.gitignore index 10a286d..efac5ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /node_modules .env .env.* +!.env.example dist/ -src/generated/prisma/ \ No newline at end of file +src/generated/prisma/ diff --git a/misson_practice/chapter10/README.md b/misson_practice/chapter10/README.md new file mode 100644 index 0000000..dfa4101 --- /dev/null +++ b/misson_practice/chapter10/README.md @@ -0,0 +1,304 @@ +# 10주차 실습: GitHub Actions와 AWS EC2로 CI/CD 구축하기 + +## 1. 실습 목표 + +- `main` 브랜치 변경을 GitHub Actions가 감지하도록 한다. +- GitHub Actions에서 애플리케이션을 빌드하고 배포 아티팩트를 만든다. +- SSH와 `rsync`를 이용해 아티팩트를 EC2로 전송한다. +- systemd로 Node.js 서버를 실행하고 재시작한다. +- 배포 환경에서도 Google OAuth 로그인이 동작하도록 콜백 URL을 분리한다. +- 구축한 파이프라인의 각 단계를 설명할 수 있도록 기록한다. + +## 2. 전체 배포 흐름 + +```text +main 브랜치 push + ↓ +GitHub Actions build job + - npm ci + - Prisma Client 및 TSOA 라우트 생성 + - TypeScript 컴파일 + - 운영 의존성만 남기기 + - artifact.tgz 생성 + ↓ +GitHub Actions deploy job + - SSH 설정 + - EC2로 아티팩트 전송 + - EC2에 .env 생성 + - current 디렉터리 교체 + - systemd 서비스 재시작 + - 서비스와 HTTP 응답 확인 +``` + +## 3. 로컬 프로젝트를 배포 구조로 변경 + +### 3-1. 실행 스크립트 분리 + +개발 환경에서는 `tsx`로 TypeScript를 바로 실행하지만, 배포 환경에서는 먼저 JavaScript로 컴파일한 후 `node`로 실행한다. + +```json +{ + "scripts": { + "build": "prisma generate --schema=./prisma/schema.prisma && tsoa spec-and-routes && tsc", + "start": "node dist/index.js", + "dev": "prisma generate --schema=./prisma/schema.prisma && tsoa spec-and-routes && nodemon --exec tsx src/index.ts" + } +} +``` + +모듈 형식은 TSOA가 생성하는 라우트와 맞도록 CommonJS로 통일했다. + +### 3-2. 빌드 확인 + +```bash +npm ci +npm run build +``` + +`dist/index.js`가 생성되고 명령이 오류 없이 끝나는지 확인한다. + +- [x] 의존성 잠금 파일 기준 설치 +- [x] Prisma Client 생성 +- [x] TSOA 라우트 생성 +- [x] TypeScript 컴파일 + +## 4. Google OAuth 콜백 URL 환경변수화 + +로컬 주소를 코드에 고정하면 EC2 배포 후에도 Google이 `localhost`로 이동한다. 따라서 `src/auth.config.ts`에서 콜백 URL을 환경변수로 받도록 변경했다. + +```ts +callbackURL: + process.env.PASSPORT_GOOGLE_CALLBACK_URL ?? + "http://localhost:3000/oauth2/callback/google"; +``` + +로컬 환경에서는 다음 값을 사용한다. + +```dotenv +PASSPORT_GOOGLE_CALLBACK_URL=http://localhost:3000/oauth2/callback/google +``` + +EC2 실습 환경에서는 다음과 같이 설정한다. + +```dotenv +PASSPORT_GOOGLE_CALLBACK_URL=http://:3000/oauth2/callback/google +``` + +> 실제 서비스에서는 도메인과 HTTPS를 적용한 `https://api.example.com/oauth2/callback/google` 형태를 사용한다. + +## 5. EC2 준비 + +### 5-1. 인스턴스와 네트워크 + +- [ ] Ubuntu EC2 인스턴스 생성 +- [ ] 탄력적 IP 연결 +- [ ] 보안 그룹에서 SSH 22번 포트를 내 IP에만 허용 +- [ ] 실습용 API 3000번 포트 허용 +- [ ] SSH 접속 확인 + +운영 환경에서는 애플리케이션의 3000번 포트를 직접 공개하지 않고, Nginx가 80/443번 요청을 받아 내부 3000번 포트로 전달하도록 구성하는 것이 좋다. + +### 5-2. Node.js 24 설치 + +```bash +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash +source ~/.bashrc +nvm install 24 +nvm use 24 +node -v +npm -v +``` + +### 5-3. MySQL 설치 + +워크북 실습에서는 비용과 구성을 단순화하기 위해 같은 EC2에 MySQL을 설치한다. + +```bash +sudo apt update +sudo apt install -y mysql-server +sudo systemctl enable --now mysql +sudo mysql -u root +``` + +```sql +ALTER USER 'root'@'localhost' + IDENTIFIED WITH mysql_native_password BY ''; +CREATE DATABASE umc_10th; +EXIT; +``` + +- [ ] MySQL 서비스가 `active`인지 확인 +- [ ] `umc_10th` 데이터베이스 생성 +- [ ] 비밀번호를 공개 문서나 저장소에 기록하지 않기 + +## 6. GitHub Actions Secrets 설정 + +GitHub 저장소의 `Settings → Secrets and variables → Actions`에서 다음 Repository secrets를 등록한다. + +| Secret | 값 | +| --- | --- | +| `EC2_HOST` | EC2 탄력적 IP | +| `EC2_PORT` | SSH 포트, 기본값 `22` | +| `EC2_USER` | Ubuntu AMI 기준 `ubuntu` | +| `EC2_SSH_KEY` | EC2 생성 시 받은 PEM 파일 전체 내용 | +| `EC2_DOT_ENV` | EC2에서 사용할 `.env` 전체 내용 | + +`EC2_DOT_ENV` 예시: + +```dotenv +PORT=3000 +DATABASE_URL=mysql://root:@localhost:3306/umc_10th +DB_HOST=localhost +DB_PORT=3306 +DB_USER=root +DB_PASSWORD= +DB_NAME=umc_10th +PASSPORT_GOOGLE_CLIENT_ID= +PASSPORT_GOOGLE_CLIENT_SECRET= +PASSPORT_GOOGLE_CALLBACK_URL=http://:3000/oauth2/callback/google +JWT_SECRET= +``` + +주의 사항: + +- 실제 Secret 값을 README, 커밋, Actions 로그에 붙이지 않는다. +- PEM 키는 시작 줄과 끝 줄을 포함한 전체 내용을 등록한다. +- DB 비밀번호에 URL 예약 문자가 있다면 `DATABASE_URL`에서 URL 인코딩한다. + +## 7. GitHub Actions 워크플로 + +워크플로 파일은 `.github/workflows/deploy-main.yml`에 작성했다. + +### build job + +1. 저장소 checkout +2. Node.js 24 설정 +3. `npm ci`로 잠금 파일과 같은 의존성 설치 +4. 빌드용 `.env` 생성 +5. `npm run build` +6. 개발 의존성 제거 +7. `dist`, `node_modules`, 패키지 파일을 아티팩트로 압축 + +### deploy job + +1. build job의 아티팩트 다운로드 +2. SSH 개인 키와 known hosts 설정 +3. `/opt/app/umc10th/incoming`으로 파일 전송 +4. Secret을 이용해 런타임 `.env` 생성 +5. 기존 `current`를 `previous`로 옮기고 새 릴리스를 `current`로 전환 +6. `umc10th.service` 등록 및 재시작 +7. systemd 상태와 `http://127.0.0.1:3000/` 응답 확인 + +## 8. 첫 배포 + +1. Chapter 10 변경사항을 원격 브랜치에 push한다. +2. Pull Request를 통해 `main`에 병합한다. +3. GitHub의 Actions 탭에서 `deploy-main`을 연다. +4. build와 deploy job이 모두 초록색인지 확인한다. +5. EC2에서 다음 명령으로 상태를 확인한다. + +```bash +sudo systemctl status umc10th --no-pager +sudo journalctl -u umc10th -n 100 --no-pager +curl http://127.0.0.1:3000/ +``` + +외부에서도 확인한다. + +```text +http://:3000/ +``` + +- [ ] Actions build 성공 화면 기록 +- [ ] Actions deploy 성공 화면 기록 +- [ ] EC2 systemd `active` 화면 기록 +- [ ] 외부 API 응답 화면 기록 + +## 9. 배포 환경에서 Google 로그인 수정 + +Google Cloud Console의 OAuth Client 설정에서 승인된 리디렉션 URI를 추가한다. + +```text +http://:3000/oauth2/callback/google +``` + +그다음 브라우저에서 아래 주소로 접속한다. + +```text +http://:3000/oauth2/login/google +``` + +- [ ] Google 로그인 화면으로 이동 +- [ ] 로그인 후 EC2 콜백 주소로 복귀 +- [ ] Access Token과 Refresh Token 응답 확인 +- [ ] Access Token으로 `/mypage` 호출 성공 + +## 10. 실습 인증 이미지 계획 + +실제 진행 중 아래 이름으로 이미지를 추가한다. + +```text +misson_practice/chapter10/images/ +├── 01-ec2-instance.png +├── 02-elastic-ip.png +├── 03-node-mysql-install.png +├── 04-github-secrets.png +├── 05-actions-build-success.png +├── 06-actions-deploy-success.png +├── 07-systemd-active.png +├── 08-api-response.png +├── 09-google-callback-setting.png +└── 10-google-login-success.png +``` + +Secret 값, PEM 키, DB 비밀번호, 토큰은 반드시 가리고 캡처한다. + +## 11. 트러블슈팅 + +### 이슈 1: 워크북 YAML의 `npm run build`가 실패함 + +**문제** + +기존 프로젝트는 `tsx src/index.ts`로 바로 실행했으며 `build` 스크립트가 없었다. 워크북 예제는 `dist` 디렉터리가 이미 생성된다고 가정하고 있었다. + +**해결** + +`prisma generate → tsoa spec-and-routes → tsc` 순서의 build 스크립트를 만들고, start는 `node dist/index.js`를 실행하도록 분리했다. + +### 이슈 2: TSOA 생성 라우트가 ESM 빌드에서 해석되지 않음 + +**문제** + +TSOA가 생성한 import 경로에는 `.js` 확장자가 없어서 NodeNext 모듈 해석에서 컴파일 오류가 발생했다. + +**해결** + +현재 프로젝트의 TSOA 생성 방식에 맞춰 TypeScript 출력과 Node 런타임 모듈 형식을 CommonJS로 통일했다. + +### 이슈 3: 배포 후 Google 로그인이 localhost로 이동함 + +**문제** + +Google Strategy의 callback URL이 `http://localhost:3000`으로 하드코딩되어 있었다. + +**해결** + +`PASSPORT_GOOGLE_CALLBACK_URL` 환경변수를 추가하고 Google Cloud Console에도 EC2 콜백 URI를 등록한다. + +## 12. 시니어 미션: 무중단 배포 + +현재 systemd의 `restart` 방식은 짧은 다운타임이 생긴다. 기본 미션을 완료한 후 PM2 cluster mode와 `pm2 reload`를 적용하면 기존 프로세스가 요청을 처리하는 동안 새 프로세스를 띄워 다운타임을 줄일 수 있다. + +추가로 확인할 항목: + +- `ecosystem.config.cjs` 작성 +- PM2 cluster mode 적용 +- `pm2 startup`과 `pm2 save` 설정 +- Actions 배포 단계에서 `pm2 reload` 실행 +- 연속 요청 중 실패가 없는지 검증 + +## 13. 회고 + +이번 실습에서 CI와 CD를 하나의 막연한 자동화가 아니라 build와 deploy라는 서로 다른 책임으로 나누어 이해할 수 있었다. 특히 GitHub Actions에서 한 번 만든 아티팩트를 EC2로 전달하므로, 작은 EC2 인스턴스가 직접 의존성을 설치하고 TypeScript를 컴파일하는 부담을 줄일 수 있었다. + +또한 로컬에서 정상 동작하던 OAuth도 배포 주소와 Google Console 설정이 함께 바뀌어야 한다는 점을 확인했다. 코드, 인프라, 외부 서비스 설정이 모두 일치해야 실제 배포가 완성된다. diff --git a/package-lock.json b/package-lock.json index 1241264..a45b353 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,8 @@ "@types/passport-jwt": "^4.0.1", "nodemon": "^3.1.14", "prisma": "^7.8.0", - "tsx": "^4.21.0" + "tsx": "^4.21.0", + "typescript": "^5.9.3" } }, "node_modules/@electric-sql/pglite": { diff --git a/package.json b/package.json index 36ec866..5afa996 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,9 @@ "description": "INHA UMC 10th_Node.js 미션, 키워드 인증 레포지터리입니다!", "main": "index.js", "scripts": { - "start": "npx prisma generate --schema=./prisma/schema.prisma && tsoa spec-and-routes && tsx src/index.ts", - "dev": "npx prisma generate --schema=./prisma/schema.prisma && tsoa spec-and-routes && nodemon --exec tsx src/index.ts" + "build": "prisma generate --schema=./prisma/schema.prisma && tsoa spec-and-routes && tsc", + "start": "node dist/index.js", + "dev": "prisma generate --schema=./prisma/schema.prisma && tsoa spec-and-routes && nodemon --exec tsx src/index.ts" }, "repository": { "type": "git", @@ -14,7 +15,7 @@ "keywords": [], "author": "", "license": "ISC", - "type": "module", + "type": "commonjs", "bugs": { "url": "https://github.com/inhadissolve/10th_Node.js/issues" }, @@ -43,6 +44,7 @@ "@types/passport-jwt": "^4.0.1", "nodemon": "^3.1.14", "prisma": "^7.8.0", - "tsx": "^4.21.0" + "tsx": "^4.21.0", + "typescript": "^5.9.3" } } diff --git a/src/auth.config.ts b/src/auth.config.ts index 0d08f09..f066754 100644 --- a/src/auth.config.ts +++ b/src/auth.config.ts @@ -91,7 +91,9 @@ export const googleStrategy = new GoogleStrategy( { clientID: process.env.PASSPORT_GOOGLE_CLIENT_ID!, clientSecret: process.env.PASSPORT_GOOGLE_CLIENT_SECRET!, - callbackURL: "http://localhost:3000/oauth2/callback/google", + callbackURL: + process.env.PASSPORT_GOOGLE_CALLBACK_URL ?? + "http://localhost:3000/oauth2/callback/google", scope: ["email", "profile"], }, async (_accessToken, _refreshToken, profile, cb) => { @@ -138,4 +140,4 @@ export const jwtStrategy = new JwtStrategy( return done(err, false); } } -); \ No newline at end of file +); diff --git a/tsconfig.json b/tsconfig.json index fc94250..90d2f93 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "libReplacement": true, /* Enable lib replacement. */ @@ -26,9 +26,9 @@ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "commonjs" /* Specify what module code is generated. */, + "module": "CommonJS" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "Node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ "rootDir": "src" /* Allow multiple folders to be treated as one when resolving modules. */, From 84075f23782a87ac7ec0b04d6aa60f796ba832ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=B0=AC=ED=98=81?= Date: Fri, 19 Jun 2026 01:44:33 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20=EB=B0=B0=ED=8F=AC=20=EC=8B=9C=20DB?= =?UTF-8?q?=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 4 +- .github/workflows/deploy-main.yml | 8 ++- misson_practice/chapter10/README.md | 21 ++++--- package-lock.json | 85 +---------------------------- package.json | 5 +- 5 files changed, 27 insertions(+), 96 deletions(-) diff --git a/.env.example b/.env.example index 7acfe0c..b1473da 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,10 @@ PORT=3000 # MySQL connection used by Prisma -DATABASE_URL=mysql://root:change-me@localhost:3306/umc_10th +DATABASE_URL=mysql://umc_app:change-me@localhost:3306/umc_10th DB_HOST=localhost DB_PORT=3306 -DB_USER=root +DB_USER=umc_app DB_PASSWORD=change-me DB_NAME=umc_10th diff --git a/.github/workflows/deploy-main.yml b/.github/workflows/deploy-main.yml index a1bdc0c..d727871 100644 --- a/.github/workflows/deploy-main.yml +++ b/.github/workflows/deploy-main.yml @@ -47,7 +47,8 @@ jobs: mkdir -p artifact cp -r dist artifact/dist cp -r node_modules artifact/node_modules - cp package.json package-lock.json artifact/ + cp -r prisma artifact/prisma + cp package.json package-lock.json prisma.config.ts artifact/ tar -czf artifact.tgz -C artifact . - name: Upload artifact @@ -108,6 +109,11 @@ jobs: run: | printf '%s' "$EC2_DOT_ENV" | base64 -w 0 | ssh target 'base64 -d > /opt/app/umc10th/incoming/.env && chmod 600 /opt/app/umc10th/incoming/.env' + - name: Apply database migrations + shell: bash + run: | + ssh target "cd /opt/app/umc10th/incoming && source /home/ubuntu/.nvm/nvm.sh && npm run migrate:deploy" + - name: Activate release shell: bash run: | diff --git a/misson_practice/chapter10/README.md b/misson_practice/chapter10/README.md index dfa4101..a87a5e1 100644 --- a/misson_practice/chapter10/README.md +++ b/misson_practice/chapter10/README.md @@ -121,14 +121,18 @@ sudo mysql -u root ``` ```sql -ALTER USER 'root'@'localhost' - IDENTIFIED WITH mysql_native_password BY ''; -CREATE DATABASE umc_10th; +CREATE DATABASE umc_10th + CHARACTER SET utf8mb4 + COLLATE utf8mb4_unicode_ci; +CREATE USER 'umc_app'@'localhost' IDENTIFIED BY ''; +GRANT ALL PRIVILEGES ON umc_10th.* TO 'umc_app'@'localhost'; +FLUSH PRIVILEGES; EXIT; ``` - [ ] MySQL 서비스가 `active`인지 확인 - [ ] `umc_10th` 데이터베이스 생성 +- [ ] `umc_app` 전용 사용자 생성 및 접속 확인 - [ ] 비밀번호를 공개 문서나 저장소에 기록하지 않기 ## 6. GitHub Actions Secrets 설정 @@ -147,10 +151,10 @@ GitHub 저장소의 `Settings → Secrets and variables → Actions`에서 다 ```dotenv PORT=3000 -DATABASE_URL=mysql://root:@localhost:3306/umc_10th +DATABASE_URL=mysql://umc_app:@localhost:3306/umc_10th DB_HOST=localhost DB_PORT=3306 -DB_USER=root +DB_USER=umc_app DB_PASSWORD= DB_NAME=umc_10th PASSPORT_GOOGLE_CLIENT_ID= @@ -185,9 +189,10 @@ JWT_SECRET= 2. SSH 개인 키와 known hosts 설정 3. `/opt/app/umc10th/incoming`으로 파일 전송 4. Secret을 이용해 런타임 `.env` 생성 -5. 기존 `current`를 `previous`로 옮기고 새 릴리스를 `current`로 전환 -6. `umc10th.service` 등록 및 재시작 -7. systemd 상태와 `http://127.0.0.1:3000/` 응답 확인 +5. Prisma migration 적용 +6. 기존 `current`를 `previous`로 옮기고 새 릴리스를 `current`로 전환 +7. `umc10th.service` 등록 및 재시작 +8. systemd 상태와 `http://127.0.0.1:3000/` 응답 확인 ## 8. 첫 배포 diff --git a/package-lock.json b/package-lock.json index a45b353..17c3037 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.1", + "prisma": "^7.8.0", "tsoa": "^7.0.0-alpha.0" }, "devDependencies": { @@ -31,7 +32,6 @@ "@types/passport-google-oauth20": "^2.0.17", "@types/passport-jwt": "^4.0.1", "nodemon": "^3.1.14", - "prisma": "^7.8.0", "tsx": "^4.21.0", "typescript": "^5.9.3" } @@ -40,14 +40,12 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", - "devOptional": true, "license": "Apache-2.0" }, "node_modules/@electric-sql/pglite-socket": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.1.tgz", "integrity": "sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw==", - "devOptional": true, "license": "Apache-2.0", "bin": { "pglite-server": "dist/scripts/server.js" @@ -60,7 +58,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.1.tgz", "integrity": "sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA==", - "devOptional": true, "license": "Apache-2.0", "peerDependencies": { "@electric-sql/pglite": "0.4.1" @@ -828,7 +825,6 @@ "version": "1.19.11", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=18.14.1" @@ -858,7 +854,6 @@ "version": "0.3.4", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", - "devOptional": true, "license": "MIT" }, "node_modules/@pkgjs/parseargs": { @@ -915,7 +910,6 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.8.0.tgz", "integrity": "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==", - "devOptional": true, "license": "Apache-2.0", "dependencies": { "c12": "3.3.4", @@ -934,7 +928,6 @@ "version": "0.24.3", "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.3.tgz", "integrity": "sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==", - "devOptional": true, "license": "ISC", "dependencies": { "@electric-sql/pglite": "0.4.1", @@ -969,7 +962,6 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz", "integrity": "sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw==", - "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -983,14 +975,12 @@ "version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a.tgz", "integrity": "sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==", - "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz", "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==", - "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "7.8.0" @@ -1000,7 +990,6 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.8.0.tgz", "integrity": "sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ==", - "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "7.8.0", @@ -1012,7 +1001,6 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz", "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==", - "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "7.8.0" @@ -1022,7 +1010,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", - "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "7.2.0" @@ -1032,21 +1019,18 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", - "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/query-plan-executor": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz", "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==", - "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/streams-local": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.2.tgz", "integrity": "sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg==", - "devOptional": true, "license": "Apache-2.0", "dependencies": { "ajv": "^8.12.0", @@ -1063,7 +1047,6 @@ "version": "0.27.3", "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.27.3.tgz", "integrity": "sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw==", - "devOptional": true, "license": "Apache-2.0", "dependencies": { "@radix-ui/react-toggle": "1.1.10", @@ -1083,14 +1066,12 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "devOptional": true, "license": "MIT" }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1106,7 +1087,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", - "devOptional": true, "license": "MIT", "dependencies": { "@radix-ui/react-slot": "1.2.3" @@ -1130,7 +1110,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "devOptional": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -1149,7 +1128,6 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", - "devOptional": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1175,7 +1153,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "devOptional": true, "license": "MIT", "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", @@ -1195,7 +1172,6 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "devOptional": true, "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -1214,7 +1190,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1230,7 +1205,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "devOptional": true, "license": "MIT" }, "node_modules/@tsoa/cli": { @@ -1871,7 +1845,6 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -1914,7 +1887,6 @@ "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "devOptional": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1975,7 +1947,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 6.0.0" @@ -2016,7 +1987,6 @@ "version": "2.9.2", "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.9.2.tgz", "integrity": "sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==", - "devOptional": true, "license": "MIT" }, "node_modules/binary-extensions": { @@ -2101,7 +2071,6 @@ "version": "3.3.4", "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", - "devOptional": true, "license": "MIT", "dependencies": { "chokidar": "^5.0.0", @@ -2130,7 +2099,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "devOptional": true, "license": "MIT", "dependencies": { "readdirp": "^5.0.0" @@ -2146,7 +2114,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -2189,7 +2156,6 @@ "version": "4.5.1", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", - "devOptional": true, "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" @@ -2332,7 +2298,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "devOptional": true, "license": "MIT" }, "node_modules/content-disposition": { @@ -2429,7 +2394,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, "license": "MIT", "peer": true }, @@ -2454,7 +2418,6 @@ "version": "7.1.5", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", - "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=16.0.0" @@ -2464,7 +2427,6 @@ "version": "6.1.7", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", - "devOptional": true, "license": "MIT" }, "node_modules/denque": { @@ -2489,7 +2451,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "devOptional": true, "license": "MIT" }, "node_modules/destroy": { @@ -2553,7 +2514,6 @@ "version": "3.20.0", "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz", "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==", - "devOptional": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -2570,7 +2530,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=14" @@ -2589,7 +2548,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", - "devOptional": true, "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -2741,14 +2699,12 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "devOptional": true, "license": "MIT" }, "node_modules/fast-check": { "version": "3.23.2", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", - "devOptional": true, "funding": [ { "type": "individual", @@ -2771,14 +2727,12 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "devOptional": true, "license": "MIT" }, "node_modules/fast-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "devOptional": true, "funding": [ { "type": "github", @@ -2901,7 +2855,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "devOptional": true, "license": "MIT", "dependencies": { "is-property": "^1.0.2" @@ -2944,7 +2897,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", - "devOptional": true, "license": "MIT" }, "node_modules/get-proto": { @@ -2977,7 +2929,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz", "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==", - "devOptional": true, "license": "MIT", "bin": { "giget": "dist/cli.mjs" @@ -3069,14 +3020,12 @@ "version": "3.1.12", "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz", "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==", - "devOptional": true, "license": "MIT" }, "node_modules/graphmatch": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz", "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==", - "devOptional": true, "license": "MIT" }, "node_modules/handlebars": { @@ -3138,7 +3087,6 @@ "version": "4.12.18", "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=16.9.0" @@ -3168,7 +3116,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", - "devOptional": true, "license": "MIT" }, "node_modules/iconv-lite": { @@ -3274,7 +3221,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", - "devOptional": true, "license": "MIT" }, "node_modules/is-what": { @@ -3314,7 +3260,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -3324,7 +3269,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "devOptional": true, "license": "MIT" }, "node_modules/jsonfile": { @@ -3428,7 +3372,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "devOptional": true, "license": "Apache-2.0" }, "node_modules/lru-cache": { @@ -3441,7 +3384,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", - "devOptional": true, "license": "MIT", "engines": { "bun": ">=1.0.0", @@ -3659,7 +3601,6 @@ "version": "3.15.3", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", - "devOptional": true, "license": "MIT", "dependencies": { "aws-ssl-profiles": "^1.1.1", @@ -3680,7 +3621,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", - "devOptional": true, "license": "MIT", "dependencies": { "lru.min": "^1.1.0" @@ -3774,7 +3714,6 @@ "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "devOptional": true, "license": "MIT" }, "node_modules/on-finished": { @@ -3929,7 +3868,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "devOptional": true, "license": "MIT" }, "node_modules/pause": { @@ -3941,7 +3879,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", - "devOptional": true, "license": "MIT" }, "node_modules/picomatch": { @@ -3961,7 +3898,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", - "devOptional": true, "license": "MIT", "dependencies": { "confbox": "^0.2.4", @@ -3973,7 +3909,6 @@ "version": "3.4.7", "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", - "devOptional": true, "license": "Unlicense", "engines": { "node": ">=12" @@ -3987,7 +3922,6 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz", "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==", - "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -4021,7 +3955,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "devOptional": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -4033,7 +3966,6 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "devOptional": true, "license": "ISC" }, "node_modules/proxy-addr": { @@ -4060,7 +3992,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "devOptional": true, "funding": [ { "type": "individual", @@ -4116,7 +4047,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", - "devOptional": true, "license": "MIT", "dependencies": { "defu": "^6.1.6", @@ -4127,7 +4057,6 @@ "version": "19.2.6", "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", - "devOptional": true, "license": "MIT", "peer": true, "engines": { @@ -4138,7 +4067,6 @@ "version": "19.2.6", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", - "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -4171,7 +4099,6 @@ "version": "2.33.4", "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==", - "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/remeda" @@ -4190,7 +4117,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4210,7 +4136,6 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 4" @@ -4248,7 +4173,6 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "devOptional": true, "license": "MIT", "peer": true }, @@ -4293,8 +4217,7 @@ "node_modules/seq-queue": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", - "devOptional": true + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" }, "node_modules/serve-static": { "version": "2.2.1", @@ -4452,7 +4375,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -4471,7 +4393,6 @@ "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "devOptional": true, "license": "MIT" }, "node_modules/string-width": { @@ -4751,7 +4672,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==", - "devOptional": true, "license": "MIT", "peerDependencies": { "typescript": ">=5" @@ -4994,7 +4914,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==", - "devOptional": true, "license": "MIT", "dependencies": { "grammex": "^3.1.11", diff --git a/package.json b/package.json index 5afa996..ada7520 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "build": "prisma generate --schema=./prisma/schema.prisma && tsoa spec-and-routes && tsc", "start": "node dist/index.js", - "dev": "prisma generate --schema=./prisma/schema.prisma && tsoa spec-and-routes && nodemon --exec tsx src/index.ts" + "dev": "prisma generate --schema=./prisma/schema.prisma && tsoa spec-and-routes && nodemon --exec tsx src/index.ts", + "migrate:deploy": "prisma migrate deploy --schema=./prisma/schema.prisma" }, "repository": { "type": "git", @@ -32,6 +33,7 @@ "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.1", + "prisma": "^7.8.0", "tsoa": "^7.0.0-alpha.0" }, "devDependencies": { @@ -43,7 +45,6 @@ "@types/passport-google-oauth20": "^2.0.17", "@types/passport-jwt": "^4.0.1", "nodemon": "^3.1.14", - "prisma": "^7.8.0", "tsx": "^4.21.0", "typescript": "^5.9.3" } From 358594e40960e50806c6bc0045a5da89fbd1dc6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=B0=AC=ED=98=81?= Date: Fri, 19 Jun 2026 03:26:48 +0900 Subject: [PATCH 7/7] =?UTF-8?q?docs:=2010=EC=A3=BC=EC=B0=A8=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EA=B2=B0=EA=B3=BC=EC=99=80=20=ED=8A=B8=EB=9F=AC?= =?UTF-8?q?=EB=B8=94=EC=8A=88=ED=8C=85=20=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- misson_practice/chapter10/README.md | 108 +++++++++++++++++++++------- 1 file changed, 83 insertions(+), 25 deletions(-) diff --git a/misson_practice/chapter10/README.md b/misson_practice/chapter10/README.md index a87a5e1..e6181d0 100644 --- a/misson_practice/chapter10/README.md +++ b/misson_practice/chapter10/README.md @@ -78,10 +78,10 @@ callbackURL: PASSPORT_GOOGLE_CALLBACK_URL=http://localhost:3000/oauth2/callback/google ``` -EC2 실습 환경에서는 다음과 같이 설정한다. +배포 환경에서는 Google OAuth 정책에 맞춰 HTTPS 도메인을 사용한다. ```dotenv -PASSPORT_GOOGLE_CALLBACK_URL=http://:3000/oauth2/callback/google +PASSPORT_GOOGLE_CALLBACK_URL=https:///oauth2/callback/google ``` > 실제 서비스에서는 도메인과 HTTPS를 적용한 `https://api.example.com/oauth2/callback/google` 형태를 사용한다. @@ -90,11 +90,12 @@ PASSPORT_GOOGLE_CALLBACK_URL=http://:3000/oauth2/callback/google ### 5-1. 인스턴스와 네트워크 -- [ ] Ubuntu EC2 인스턴스 생성 -- [ ] 탄력적 IP 연결 -- [ ] 보안 그룹에서 SSH 22번 포트를 내 IP에만 허용 -- [ ] 실습용 API 3000번 포트 허용 -- [ ] SSH 접속 확인 +- [x] Ubuntu EC2 인스턴스 생성 +- [x] 탄력적 IP 연결 +- [x] 보안 그룹에서 SSH 22번 포트를 내 IP에만 허용 +- [x] Nginx용 HTTP 80, HTTPS 443 포트 허용 +- [x] 애플리케이션 3000번 외부 규칙 제거 +- [x] SSH 접속 확인 운영 환경에서는 애플리케이션의 3000번 포트를 직접 공개하지 않고, Nginx가 80/443번 요청을 받아 내부 3000번 포트로 전달하도록 구성하는 것이 좋다. @@ -130,10 +131,19 @@ FLUSH PRIVILEGES; EXIT; ``` -- [ ] MySQL 서비스가 `active`인지 확인 -- [ ] `umc_10th` 데이터베이스 생성 -- [ ] `umc_app` 전용 사용자 생성 및 접속 확인 -- [ ] 비밀번호를 공개 문서나 저장소에 기록하지 않기 +- [x] MySQL 서비스가 `active`인지 확인 +- [x] `umc_10th` 데이터베이스 생성 +- [x] `umc_app` 전용 사용자 생성 및 접속 확인 +- [x] 비밀번호를 공개 문서나 저장소에 기록하지 않기 + +메모리가 약 1GiB인 인스턴스에서 Prisma migration과 MySQL을 함께 실행할 수 있도록 1GiB swap을 추가했다. + +```bash +sudo fallocate -l 1G /swapfile +sudo chmod 600 /swapfile +sudo mkswap /swapfile +sudo swapon /swapfile +``` ## 6. GitHub Actions Secrets 설정 @@ -159,7 +169,7 @@ DB_PASSWORD= DB_NAME=umc_10th PASSPORT_GOOGLE_CLIENT_ID= PASSPORT_GOOGLE_CLIENT_SECRET= -PASSPORT_GOOGLE_CALLBACK_URL=http://:3000/oauth2/callback/google +PASSPORT_GOOGLE_CALLBACK_URL=https:///oauth2/callback/google JWT_SECRET= ``` @@ -208,35 +218,43 @@ sudo journalctl -u umc10th -n 100 --no-pager curl http://127.0.0.1:3000/ ``` -외부에서도 확인한다. +외부에서는 Nginx와 HTTPS 도메인을 통해 확인한다. ```text -http://:3000/ +https:/// ``` -- [ ] Actions build 성공 화면 기록 -- [ ] Actions deploy 성공 화면 기록 -- [ ] EC2 systemd `active` 화면 기록 -- [ ] 외부 API 응답 화면 기록 +- [x] Actions build 성공 +- [x] Actions deploy 성공 +- [x] EC2 systemd `active` 확인 +- [x] 외부 HTTPS API 응답 확인 + +### Nginx와 HTTPS + +DuckDNS 서브도메인을 탄력적 IP에 연결하고 Nginx가 80/443 요청을 내부 3000번 포트로 전달하도록 구성했다. Certbot으로 TLS 인증서를 발급했으며 `certbot renew --dry-run`도 성공했다. + +```text +Client → HTTPS :443 → Nginx → HTTP 127.0.0.1:3000 → Node.js +``` ## 9. 배포 환경에서 Google 로그인 수정 Google Cloud Console의 OAuth Client 설정에서 승인된 리디렉션 URI를 추가한다. ```text -http://:3000/oauth2/callback/google +https:///oauth2/callback/google ``` 그다음 브라우저에서 아래 주소로 접속한다. ```text -http://:3000/oauth2/login/google +https:///oauth2/login/google ``` -- [ ] Google 로그인 화면으로 이동 -- [ ] 로그인 후 EC2 콜백 주소로 복귀 -- [ ] Access Token과 Refresh Token 응답 확인 -- [ ] Access Token으로 `/mypage` 호출 성공 +- [x] Google 로그인 화면으로 이동 +- [x] 로그인 후 HTTPS 콜백 주소로 복귀 +- [x] Access Token과 Refresh Token 응답 확인 +- [x] Access Token으로 `/mypage` 호출 성공 ## 10. 실습 인증 이미지 계획 @@ -288,7 +306,47 @@ Google Strategy의 callback URL이 `http://localhost:3000`으로 하드코딩되 **해결** -`PASSPORT_GOOGLE_CALLBACK_URL` 환경변수를 추가하고 Google Cloud Console에도 EC2 콜백 URI를 등록한다. +`PASSPORT_GOOGLE_CALLBACK_URL` 환경변수를 추가하고 Google Cloud Console에도 HTTPS 도메인 콜백 URI를 등록했다. + +### 이슈 4: Repository secrets가 없어 빌드 환경변수가 비어 있음 + +**문제** + +GitHub Actions의 Prisma generate 단계에서 `DATABASE_URL`을 찾을 수 없었다. 확인 결과 Repository secrets가 등록되지 않은 상태였다. + +**해결** + +실습 저장소의 Repository secrets에 `EC2_HOST`, `EC2_PORT`, `EC2_USER`, `EC2_SSH_KEY`, `EC2_DOT_ENV`를 등록했다. + +### 이슈 5: Prisma가 MySQL 인증 오류를 다른 plugin 오류로 표시함 + +**문제** + +DB 비밀번호 불일치 상황에서 Prisma가 `Unknown authentication plugin sha256_password`를 출력해 원인 파악이 어려웠다. + +**해결** + +`umc_app` 비밀번호를 재설정하고 같은 값을 `DATABASE_URL`, `DB_PASSWORD`에 반영했다. MySQL 인증 방식은 `mysql_native_password`로 통일했다. + +### 이슈 6: 작은 EC2에서 migration 중 DB 연결 실패 + +**문제** + +MySQL과 Node.js가 실행 중인 약 1GiB 인스턴스의 가용 메모리가 100MiB 미만이었고 swap도 없었다. Prisma migration 중 일시적으로 `P1001` 연결 실패가 발생했다. + +**해결** + +기존 EBS 볼륨에 1GiB swap을 만들고 migration을 다시 실행했다. 이후 migration, systemd 재시작, HTTP 검증이 모두 통과했다. + +### 이슈 7: Google OAuth는 공인 IP 기반 HTTP callback을 허용하지 않음 + +**문제** + +Google OAuth는 localhost를 제외한 HTTP callback과 원시 IP host를 허용하지 않는다. + +**해결** + +DuckDNS 도메인, Nginx, Certbot을 적용하고 Google Cloud Console에 HTTPS callback URI를 등록했다. ## 12. 시니어 미션: 무중단 배포