issue # nestjs 로 초기화

This commit is contained in:
geonhee-min
2025-11-21 15:18:37 +09:00
parent ce50a53256
commit 0170421d16
57 changed files with 2483 additions and 143 deletions

8
.env
View File

@@ -4,8 +4,14 @@ PGPORT=15454
PGDATABASE=scheduler PGDATABASE=scheduler
PGUSER=baekyangdan PGUSER=baekyangdan
PGPASSWORD=qwas745478! PGPASSWORD=qwas745478!
PG_DATABASE_URL=postgres://baekyangdan:qwas745478!@bkdhome.p-e.kr:15454/scheduler
# Fastify 서버 포트 # Redis 설정
RD_HOST=bkdhome.p-e.kr
RD_PORT=16779
RD_URL=redis://bkdhome.p-e.kr:16779
# Express 서버 포트
PORT=3000 PORT=3000
# Gmail SMTP 설정 # Gmail SMTP 설정

13
.gitignore vendored
View File

@@ -5,14 +5,24 @@ yarn-error.log*
package-lock.json package-lock.json
yarn.lock yarn.lock
.pnp.*
.pnp.loader.mjs
.yarn/install-state.gz
# TypeScript # TypeScript
dist/ dist/
build/
*.tsbuildinfo *.tsbuildinfo
# 환경 변수 # 환경 변수
# .env # .env
# .env.*.local # .env.*.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# IDE / OS # IDE / OS
.vscode/ .vscode/
.idea/ .idea/
@@ -31,3 +41,6 @@ Thumbs.db
# Logs # Logs
logs/ logs/
*.log *.log
.cache/
.eslintcache

View File

@@ -1,43 +1,43 @@
# This file is a template, and might need editing before it works on your project. # # This file is a template, and might need editing before it works on your project.
# This is a sample GitLab CI/CD configuration file that should run without any modifications. # # This is a sample GitLab CI/CD configuration file that should run without any modifications.
# It demonstrates a basic 3 stage CI/CD pipeline. Instead of real tests or scripts, # # It demonstrates a basic 3 stage CI/CD pipeline. Instead of real tests or scripts,
# it uses echo commands to simulate the pipeline execution. # # it uses echo commands to simulate the pipeline execution.
# # #
# A pipeline is composed of independent jobs that run scripts, grouped into stages. # # A pipeline is composed of independent jobs that run scripts, grouped into stages.
# Stages run in sequential order, but jobs within stages run in parallel. # # Stages run in sequential order, but jobs within stages run in parallel.
# # #
# For more information, see: https://docs.gitlab.com/ee/ci/yaml/#stages # # For more information, see: https://docs.gitlab.com/ee/ci/yaml/#stages
# # #
# You can copy and paste this template into a new `.gitlab-ci.yml` file. # # You can copy and paste this template into a new `.gitlab-ci.yml` file.
# You should not add this template to an existing `.gitlab-ci.yml` file by using the `include:` keyword. # # You should not add this template to an existing `.gitlab-ci.yml` file by using the `include:` keyword.
# # #
# To contribute improvements to CI/CD templates, please follow the Development guide at: # # To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/development/cicd/templates/ # # https://docs.gitlab.com/development/cicd/templates/
# This specific template is located at: # # This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml # # https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml
stages: # List of stages for jobs, and their order of execution # stages: # List of stages for jobs, and their order of execution
- build # - build
cache: # cache:
key: # key:
files: # files:
- package-lock.json # - package-lock.json
paths: # paths:
- node_modules/ # - node_modules/
build: # This job runs in the build stage, which runs first. # build: # This job runs in the build stage, which runs first.
stage: build # stage: build
tags: # tags:
- local-runner # - local-runner
before_script: # before_script:
script: # script:
- echo "Compiling the code..." # - echo "Compiling the code..."
- echo $DOCKER_VOLUME # - echo $DOCKER_VOLUME
- echo $DOCKER_COMPOSE_VOLUME # - echo $DOCKER_COMPOSE_VOLUME
- npm install # - npm install
- npm run build # - npm run build
- sudo cp -r $PWD/dist/. $DOCKER_VOLUME/scheduler/back/dist # - sudo cp -r $PWD/dist/. $DOCKER_VOLUME/scheduler/back/dist
- sudo cp $PWD/package.json $DOCKER_VOLUME/scheduler/back/dist # - sudo cp $PWD/package.json $DOCKER_VOLUME/scheduler/back/dist
- docker compose -f $DOCKER_COMPOSE_VOLUME/scheduler/docker-compose.yaml up -d back # - docker compose -f $DOCKER_COMPOSE_VOLUME/scheduler/docker-compose.yaml up -d back
- echo "Compile complete." # - echo "Compile complete."

4
.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

32
.yarn/sdks/eslint/bin/eslint.js vendored Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/bin/eslint.js
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint/bin/eslint.js your application uses
module.exports = wrapWithUserWrapper(absRequire(`eslint/bin/eslint.js`));

32
.yarn/sdks/eslint/lib/api.js vendored Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint your application uses
module.exports = wrapWithUserWrapper(absRequire(`eslint`));

32
.yarn/sdks/eslint/lib/config-api.js vendored Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/config
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint/config your application uses
module.exports = wrapWithUserWrapper(absRequire(`eslint/config`));

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/config
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint/config your application uses
module.exports = wrapWithUserWrapper(absRequire(`eslint/config`));

32
.yarn/sdks/eslint/lib/types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint your application uses
module.exports = wrapWithUserWrapper(absRequire(`eslint`));

32
.yarn/sdks/eslint/lib/types/rules.d.ts vendored Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/rules
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint/rules your application uses
module.exports = wrapWithUserWrapper(absRequire(`eslint/rules`));

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/universal
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint/universal your application uses
module.exports = wrapWithUserWrapper(absRequire(`eslint/universal`));

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/use-at-your-own-risk
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint/use-at-your-own-risk your application uses
module.exports = wrapWithUserWrapper(absRequire(`eslint/use-at-your-own-risk`));

32
.yarn/sdks/eslint/lib/universal.js vendored Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/universal
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint/universal your application uses
module.exports = wrapWithUserWrapper(absRequire(`eslint/universal`));

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/use-at-your-own-risk
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint/use-at-your-own-risk your application uses
module.exports = wrapWithUserWrapper(absRequire(`eslint/use-at-your-own-risk`));

31
.yarn/sdks/eslint/package.json vendored Normal file
View File

@@ -0,0 +1,31 @@
{
"name": "eslint",
"version": "9.39.1-sdk",
"main": "./lib/api.js",
"type": "commonjs",
"bin": {
"eslint": "./bin/eslint.js"
},
"exports": {
".": {
"types": "./lib/types/index.d.ts",
"default": "./lib/api.js"
},
"./config": {
"types": "./lib/types/config-api.d.ts",
"default": "./lib/config-api.js"
},
"./package.json": "./package.json",
"./use-at-your-own-risk": {
"types": "./lib/types/use-at-your-own-risk.d.ts",
"default": "./lib/unsupported-api.js"
},
"./rules": {
"types": "./lib/types/rules.d.ts"
},
"./universal": {
"types": "./lib/types/universal.d.ts",
"default": "./lib/universal.js"
}
}
}

5
.yarn/sdks/integrations.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
# This file is automatically generated by @yarnpkg/sdks.
# Manual changes might be lost!
integrations:
- vscode

32
.yarn/sdks/prettier/bin/prettier.cjs vendored Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier/bin/prettier.cjs
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real prettier/bin/prettier.cjs your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`));

32
.yarn/sdks/prettier/index.cjs vendored Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real prettier your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier`));

7
.yarn/sdks/prettier/package.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"name": "prettier",
"version": "3.6.2-sdk",
"main": "./index.cjs",
"type": "commonjs",
"bin": "./bin/prettier.cjs"
}

32
.yarn/sdks/typescript/bin/tsc vendored Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsc
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real typescript/bin/tsc your application uses
module.exports = wrapWithUserWrapper(absRequire(`typescript/bin/tsc`));

32
.yarn/sdks/typescript/bin/tsserver vendored Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsserver
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real typescript/bin/tsserver your application uses
module.exports = wrapWithUserWrapper(absRequire(`typescript/bin/tsserver`));

32
.yarn/sdks/typescript/lib/tsc.js vendored Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsc.js
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real typescript/lib/tsc.js your application uses
module.exports = wrapWithUserWrapper(absRequire(`typescript/lib/tsc.js`));

248
.yarn/sdks/typescript/lib/tsserver.js vendored Normal file
View File

@@ -0,0 +1,248 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsserver.js
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
const moduleWrapper = exports => {
return wrapWithUserWrapper(moduleWrapperFn(exports));
};
const moduleWrapperFn = tsserver => {
if (!process.versions.pnp) {
return tsserver;
}
const {isAbsolute} = require(`path`);
const pnpApi = require(`pnpapi`);
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
const isPortal = str => str.startsWith("portal:/");
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
return `${locator.name}@${locator.reference}`;
}));
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
// doesn't understand. This layer makes sure to remove the protocol
// before forwarding it to TS, and to add it back on all returned paths.
function toEditorPath(str) {
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
// We also take the opportunity to turn virtual paths into physical ones;
// this makes it much easier to work with workspaces that list peer
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
// file instances instead of the real ones.
//
// We only do this to modules owned by the the dependency tree roots.
// This avoids breaking the resolution when jumping inside a vendor
// with peer dep (otherwise jumping into react-dom would show resolution
// errors on react).
//
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
if (resolved) {
const locator = pnpApi.findPackageLocator(resolved);
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
str = resolved;
}
}
str = normalize(str);
if (str.match(/\.zip\//)) {
switch (hostInfo) {
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
// VSCode only adds it automatically for supported schemes,
// so we have to do it manually for the `zip` scheme.
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
//
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
//
// 2021-10-08: VSCode changed the format in 1.61.
// Before | ^zip:/c:/foo/bar.zip/package.json
// After | ^/zip//c:/foo/bar.zip/package.json
//
// 2022-04-06: VSCode changed the format in 1.66.
// Before | ^/zip//c:/foo/bar.zip/package.json
// After | ^/zip/c:/foo/bar.zip/package.json
//
// 2022-05-06: VSCode changed the format in 1.68
// Before | ^/zip/c:/foo/bar.zip/package.json
// After | ^/zip//c:/foo/bar.zip/package.json
//
case `vscode <1.61`: {
str = `^zip:${str}`;
} break;
case `vscode <1.66`: {
str = `^/zip/${str}`;
} break;
case `vscode <1.68`: {
str = `^/zip${str}`;
} break;
case `vscode`: {
str = `^/zip/${str}`;
} break;
// To make "go to definition" work,
// We have to resolve the actual file system path from virtual path
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
case `coc-nvim`: {
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = resolve(`zipfile:${str}`);
} break;
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
// We have to resolve the actual file system path from virtual path,
// everything else is up to neovim
case `neovim`: {
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = `zipfile://${str}`;
} break;
default: {
str = `zip:${str}`;
} break;
}
} else {
str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`);
}
}
return str;
}
function fromEditorPath(str) {
switch (hostInfo) {
case `coc-nvim`: {
str = str.replace(/\.zip::/, `.zip/`);
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
// So in order to convert it back, we use .* to match all the thing
// before `zipfile:`
return process.platform === `win32`
? str.replace(/^.*zipfile:\//, ``)
: str.replace(/^.*zipfile:/, ``);
} break;
case `neovim`: {
str = str.replace(/\.zip::/, `.zip/`);
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
return str.replace(/^zipfile:\/\//, ``);
} break;
case `vscode`:
default: {
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
} break;
}
}
// Force enable 'allowLocalPluginLoads'
// TypeScript tries to resolve plugins using a path relative to itself
// which doesn't work when using the global cache
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
// TypeScript already does local loads and if this code is running the user trusts the workspace
// https://github.com/microsoft/vscode/issues/45856
const ConfiguredProject = tsserver.server.ConfiguredProject;
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
this.projectService.allowLocalPluginLoads = true;
return originalEnablePluginsWithOptions.apply(this, arguments);
};
// And here is the point where we hijack the VSCode <-> TS communications
// by adding ourselves in the middle. We locate everything that looks
// like an absolute path of ours and normalize it.
const Session = tsserver.server.Session;
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
let hostInfo = `unknown`;
Object.assign(Session.prototype, {
onMessage(/** @type {string | object} */ message) {
const isStringMessage = typeof message === 'string';
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
if (
parsedMessage != null &&
typeof parsedMessage === `object` &&
parsedMessage.arguments &&
typeof parsedMessage.arguments.hostInfo === `string`
) {
hostInfo = parsedMessage.arguments.hostInfo;
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
// The RegExp from https://semver.org/ but without the caret at the start
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
) ?? []).map(Number)
if (major === 1) {
if (minor < 61) {
hostInfo += ` <1.61`;
} else if (minor < 66) {
hostInfo += ` <1.66`;
} else if (minor < 68) {
hostInfo += ` <1.68`;
}
}
}
}
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
return typeof value === 'string' ? fromEditorPath(value) : value;
});
return originalOnMessage.call(
this,
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
);
},
send(/** @type {any} */ msg) {
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
return typeof value === `string` ? toEditorPath(value) : value;
})));
}
});
return tsserver;
};
const [major, minor] = absRequire(`typescript/package.json`).version.split(`.`, 2).map(value => parseInt(value, 10));
// In TypeScript@>=5.5 the tsserver uses the public TypeScript API so that needs to be patched as well.
// Ref https://github.com/microsoft/TypeScript/pull/55326
if (major > 5 || (major === 5 && minor >= 5)) {
moduleWrapper(absRequire(`typescript`));
}
// Defer to the real typescript/lib/tsserver.js your application uses
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`));

View File

@@ -0,0 +1,248 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsserverlibrary.js
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
const moduleWrapper = exports => {
return wrapWithUserWrapper(moduleWrapperFn(exports));
};
const moduleWrapperFn = tsserver => {
if (!process.versions.pnp) {
return tsserver;
}
const {isAbsolute} = require(`path`);
const pnpApi = require(`pnpapi`);
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
const isPortal = str => str.startsWith("portal:/");
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
return `${locator.name}@${locator.reference}`;
}));
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
// doesn't understand. This layer makes sure to remove the protocol
// before forwarding it to TS, and to add it back on all returned paths.
function toEditorPath(str) {
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
// We also take the opportunity to turn virtual paths into physical ones;
// this makes it much easier to work with workspaces that list peer
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
// file instances instead of the real ones.
//
// We only do this to modules owned by the the dependency tree roots.
// This avoids breaking the resolution when jumping inside a vendor
// with peer dep (otherwise jumping into react-dom would show resolution
// errors on react).
//
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
if (resolved) {
const locator = pnpApi.findPackageLocator(resolved);
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
str = resolved;
}
}
str = normalize(str);
if (str.match(/\.zip\//)) {
switch (hostInfo) {
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
// VSCode only adds it automatically for supported schemes,
// so we have to do it manually for the `zip` scheme.
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
//
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
//
// 2021-10-08: VSCode changed the format in 1.61.
// Before | ^zip:/c:/foo/bar.zip/package.json
// After | ^/zip//c:/foo/bar.zip/package.json
//
// 2022-04-06: VSCode changed the format in 1.66.
// Before | ^/zip//c:/foo/bar.zip/package.json
// After | ^/zip/c:/foo/bar.zip/package.json
//
// 2022-05-06: VSCode changed the format in 1.68
// Before | ^/zip/c:/foo/bar.zip/package.json
// After | ^/zip//c:/foo/bar.zip/package.json
//
case `vscode <1.61`: {
str = `^zip:${str}`;
} break;
case `vscode <1.66`: {
str = `^/zip/${str}`;
} break;
case `vscode <1.68`: {
str = `^/zip${str}`;
} break;
case `vscode`: {
str = `^/zip/${str}`;
} break;
// To make "go to definition" work,
// We have to resolve the actual file system path from virtual path
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
case `coc-nvim`: {
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = resolve(`zipfile:${str}`);
} break;
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
// We have to resolve the actual file system path from virtual path,
// everything else is up to neovim
case `neovim`: {
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = `zipfile://${str}`;
} break;
default: {
str = `zip:${str}`;
} break;
}
} else {
str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`);
}
}
return str;
}
function fromEditorPath(str) {
switch (hostInfo) {
case `coc-nvim`: {
str = str.replace(/\.zip::/, `.zip/`);
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
// So in order to convert it back, we use .* to match all the thing
// before `zipfile:`
return process.platform === `win32`
? str.replace(/^.*zipfile:\//, ``)
: str.replace(/^.*zipfile:/, ``);
} break;
case `neovim`: {
str = str.replace(/\.zip::/, `.zip/`);
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
return str.replace(/^zipfile:\/\//, ``);
} break;
case `vscode`:
default: {
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
} break;
}
}
// Force enable 'allowLocalPluginLoads'
// TypeScript tries to resolve plugins using a path relative to itself
// which doesn't work when using the global cache
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
// TypeScript already does local loads and if this code is running the user trusts the workspace
// https://github.com/microsoft/vscode/issues/45856
const ConfiguredProject = tsserver.server.ConfiguredProject;
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
this.projectService.allowLocalPluginLoads = true;
return originalEnablePluginsWithOptions.apply(this, arguments);
};
// And here is the point where we hijack the VSCode <-> TS communications
// by adding ourselves in the middle. We locate everything that looks
// like an absolute path of ours and normalize it.
const Session = tsserver.server.Session;
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
let hostInfo = `unknown`;
Object.assign(Session.prototype, {
onMessage(/** @type {string | object} */ message) {
const isStringMessage = typeof message === 'string';
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
if (
parsedMessage != null &&
typeof parsedMessage === `object` &&
parsedMessage.arguments &&
typeof parsedMessage.arguments.hostInfo === `string`
) {
hostInfo = parsedMessage.arguments.hostInfo;
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
// The RegExp from https://semver.org/ but without the caret at the start
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
) ?? []).map(Number)
if (major === 1) {
if (minor < 61) {
hostInfo += ` <1.61`;
} else if (minor < 66) {
hostInfo += ` <1.66`;
} else if (minor < 68) {
hostInfo += ` <1.68`;
}
}
}
}
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
return typeof value === 'string' ? fromEditorPath(value) : value;
});
return originalOnMessage.call(
this,
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
);
},
send(/** @type {any} */ msg) {
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
return typeof value === `string` ? toEditorPath(value) : value;
})));
}
});
return tsserver;
};
const [major, minor] = absRequire(`typescript/package.json`).version.split(`.`, 2).map(value => parseInt(value, 10));
// In TypeScript@>=5.5 the tsserver uses the public TypeScript API so that needs to be patched as well.
// Ref https://github.com/microsoft/TypeScript/pull/55326
if (major > 5 || (major === 5 && minor >= 5)) {
moduleWrapper(absRequire(`typescript`));
}
// Defer to the real typescript/lib/tsserverlibrary.js your application uses
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`));

32
.yarn/sdks/typescript/lib/typescript.js vendored Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real typescript your application uses
module.exports = wrapWithUserWrapper(absRequire(`typescript`));

10
.yarn/sdks/typescript/package.json vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"name": "typescript",
"version": "5.9.3-sdk",
"main": "./lib/typescript.js",
"type": "commonjs",
"bin": {
"tsc": "./bin/tsc",
"tsserver": "./bin/tsserver"
}
}

98
README.md Normal file
View File

@@ -0,0 +1,98 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Project setup
```bash
$ yarn install
```
## Compile and run the project
```bash
# development
$ yarn run start
# watch mode
$ yarn run start:dev
# production mode
$ yarn run start:prod
```
## Run tests
```bash
# unit tests
$ yarn run test
# e2e tests
$ yarn run test:e2e
# test coverage
$ yarn run test:cov
```
## Deployment
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
```bash
$ yarn install -g @nestjs/mau
$ mau deploy
```
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
## Resources
Check out a few resources that may come in handy when working with NestJS:
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).

13
drizzle.config.ts Normal file
View File

@@ -0,0 +1,13 @@
import { defineConfig } from 'drizzle-kit';
import dotenv from 'dotenv';
dotenv.config();
export default defineConfig({
dialect: "postgresql",
schema: "./src/db/schema",
out: "./drizzle",
dbCredentials: {
url: process.env.PG_DATABASE_URL!
}
});

View File

@@ -0,0 +1,79 @@
-- Current sql file was generated after introspecting the database
-- If you want to run this migration please uncomment this code before executing migrations
/*
CREATE TABLE "comment" (
"id" uuid PRIMARY KEY NOT NULL,
"content" text,
"created_at" date,
"is_deleted" boolean DEFAULT false,
"writer_id" uuid,
"parent_id" uuid
);
--> statement-breakpoint
CREATE TABLE "schedule" (
"id" uuid PRIMARY KEY NOT NULL,
"name" varchar,
"start_at" date,
"end_at" date,
"status" varchar,
"content" text,
"is_deleted" boolean DEFAULT false,
"type" varchar,
"created_at" date,
"owner" uuid
);
--> statement-breakpoint
CREATE TABLE "account" (
"name" varchar NOT NULL,
"email" varchar NOT NULL,
"password" varchar NOT NULL,
"birthday" date,
"account_id" varchar NOT NULL,
"nickname" varchar NOT NULL,
"status" varchar DEFAULT 'wait' NOT NULL,
"is_deleted" boolean DEFAULT false NOT NULL,
"created_at" date DEFAULT now() NOT NULL,
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "participant" (
"participant_id" uuid NOT NULL,
"schedule_id" uuid NOT NULL,
"is_deleted" boolean DEFAULT false
);
--> statement-breakpoint
CREATE TABLE "favorite" (
"is_deleted" boolean DEFAULT false,
"created_at" date,
"user_id" uuid NOT NULL,
"schedule_id" uuid NOT NULL,
CONSTRAINT "favorite_pk" PRIMARY KEY("user_id","schedule_id")
);
--> statement-breakpoint
CREATE TABLE "follow" (
"is_deleted" boolean DEFAULT false,
"is_accepted" boolean DEFAULT false,
"is_linked" boolean DEFAULT false,
"created_at" date,
"following" uuid NOT NULL,
"follower" uuid NOT NULL,
CONSTRAINT "follow_pk" PRIMARY KEY("following","follower")
);
--> statement-breakpoint
ALTER TABLE "comment" ADD CONSTRAINT "writer_id" FOREIGN KEY ("writer_id") REFERENCES "public"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "comment" ADD CONSTRAINT "parent_id" FOREIGN KEY ("parent_id") REFERENCES "public"."comment"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "schedule" ADD CONSTRAINT "schedule_user_fk" FOREIGN KEY ("owner") REFERENCES "public"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "participant" ADD CONSTRAINT "schedule_id" FOREIGN KEY ("schedule_id") REFERENCES "public"."schedule"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "participant" ADD CONSTRAINT "participant_id" FOREIGN KEY ("participant_id") REFERENCES "public"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "favorite" ADD CONSTRAINT "schedule_id" FOREIGN KEY ("schedule_id") REFERENCES "public"."schedule"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "favorite" ADD CONSTRAINT "user_id" FOREIGN KEY ("user_id") REFERENCES "public"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "follow" ADD CONSTRAINT "follower_id" FOREIGN KEY ("follower") REFERENCES "public"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "follow" ADD CONSTRAINT "following_id" FOREIGN KEY ("following") REFERENCES "public"."account"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "schedule_enddatetime_idx" ON "schedule" USING btree ("end_at" date_ops);--> statement-breakpoint
CREATE INDEX "schedule_name_idx" ON "schedule" USING btree ("name" text_ops,"content" text_ops);--> statement-breakpoint
CREATE INDEX "schedule_startdatetime_idx" ON "schedule" USING btree ("start_at" date_ops);--> statement-breakpoint
CREATE INDEX "schedule_status_idx" ON "schedule" USING btree ("status" text_ops);--> statement-breakpoint
CREATE INDEX "schedule_type_idx" ON "schedule" USING btree ("type" text_ops);--> statement-breakpoint
CREATE INDEX "participant_participant_id_idx" ON "participant" USING btree ("participant_id" uuid_ops);--> statement-breakpoint
CREATE INDEX "participant_schedule_id_idx" ON "participant" USING btree ("schedule_id" uuid_ops);
*/

View File

@@ -0,0 +1,613 @@
{
"id": "00000000-0000-0000-0000-000000000000",
"prevId": "",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.comment": {
"name": "comment",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "date",
"primaryKey": false,
"notNull": false
},
"is_deleted": {
"name": "is_deleted",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"writer_id": {
"name": "writer_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"parent_id": {
"name": "parent_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"writer_id": {
"name": "writer_id",
"tableFrom": "comment",
"tableTo": "account",
"schemaTo": "public",
"columnsFrom": [
"writer_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"parent_id": {
"name": "parent_id",
"tableFrom": "comment",
"tableTo": "comment",
"schemaTo": "public",
"columnsFrom": [
"parent_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {},
"policies": {},
"isRLSEnabled": false
},
"public.schedule": {
"name": "schedule",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"start_at": {
"name": "start_at",
"type": "date",
"primaryKey": false,
"notNull": false
},
"end_at": {
"name": "end_at",
"type": "date",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": false
},
"is_deleted": {
"name": "is_deleted",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"type": {
"name": "type",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "date",
"primaryKey": false,
"notNull": false
},
"owner": {
"name": "owner",
"type": "uuid",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"schedule_enddatetime_idx": {
"name": "schedule_enddatetime_idx",
"columns": [
{
"expression": "end_at",
"asc": true,
"nulls": "last",
"opclass": "date_ops",
"isExpression": false
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"schedule_name_idx": {
"name": "schedule_name_idx",
"columns": [
{
"expression": "name",
"asc": true,
"nulls": "last",
"opclass": "text_ops",
"isExpression": false
},
{
"expression": "content",
"asc": true,
"nulls": "last",
"opclass": "text_ops",
"isExpression": false
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"schedule_startdatetime_idx": {
"name": "schedule_startdatetime_idx",
"columns": [
{
"expression": "start_at",
"asc": true,
"nulls": "last",
"opclass": "date_ops",
"isExpression": false
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"schedule_status_idx": {
"name": "schedule_status_idx",
"columns": [
{
"expression": "status",
"asc": true,
"nulls": "last",
"opclass": "text_ops",
"isExpression": false
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"schedule_type_idx": {
"name": "schedule_type_idx",
"columns": [
{
"expression": "type",
"asc": true,
"nulls": "last",
"opclass": "text_ops",
"isExpression": false
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"schedule_user_fk": {
"name": "schedule_user_fk",
"tableFrom": "schedule",
"tableTo": "account",
"schemaTo": "public",
"columnsFrom": [
"owner"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {},
"policies": {},
"isRLSEnabled": false
},
"public.account": {
"name": "account",
"schema": "",
"columns": {
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"password": {
"name": "password",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"birthday": {
"name": "birthday",
"type": "date",
"primaryKey": false,
"notNull": false
},
"account_id": {
"name": "account_id",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"nickname": {
"name": "nickname",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"status": {
"name": "status",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'wait'"
},
"is_deleted": {
"name": "is_deleted",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "date",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "uuid_generate_v4()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {},
"policies": {},
"isRLSEnabled": false
},
"public.participant": {
"name": "participant",
"schema": "",
"columns": {
"participant_id": {
"name": "participant_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"schedule_id": {
"name": "schedule_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"is_deleted": {
"name": "is_deleted",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
}
},
"indexes": {
"participant_participant_id_idx": {
"name": "participant_participant_id_idx",
"columns": [
{
"expression": "participant_id",
"asc": true,
"nulls": "last",
"opclass": "uuid_ops",
"isExpression": false
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"participant_schedule_id_idx": {
"name": "participant_schedule_id_idx",
"columns": [
{
"expression": "schedule_id",
"asc": true,
"nulls": "last",
"opclass": "uuid_ops",
"isExpression": false
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"schedule_id": {
"name": "schedule_id",
"tableFrom": "participant",
"tableTo": "schedule",
"schemaTo": "public",
"columnsFrom": [
"schedule_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"participant_id": {
"name": "participant_id",
"tableFrom": "participant",
"tableTo": "account",
"schemaTo": "public",
"columnsFrom": [
"participant_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {},
"policies": {},
"isRLSEnabled": false
},
"public.favorite": {
"name": "favorite",
"schema": "",
"columns": {
"is_deleted": {
"name": "is_deleted",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "date",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"schedule_id": {
"name": "schedule_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"schedule_id": {
"name": "schedule_id",
"tableFrom": "favorite",
"tableTo": "schedule",
"schemaTo": "public",
"columnsFrom": [
"schedule_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"user_id": {
"name": "user_id",
"tableFrom": "favorite",
"tableTo": "account",
"schemaTo": "public",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"favorite_pk": {
"name": "favorite_pk",
"columns": [
"user_id",
"schedule_id"
]
}
},
"uniqueConstraints": {},
"checkConstraints": {},
"policies": {},
"isRLSEnabled": false
},
"public.follow": {
"name": "follow",
"schema": "",
"columns": {
"is_deleted": {
"name": "is_deleted",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"is_accepted": {
"name": "is_accepted",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"is_linked": {
"name": "is_linked",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "date",
"primaryKey": false,
"notNull": false
},
"following": {
"name": "following",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"follower": {
"name": "follower",
"type": "uuid",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"follower_id": {
"name": "follower_id",
"tableFrom": "follow",
"tableTo": "account",
"schemaTo": "public",
"columnsFrom": [
"follower"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"following_id": {
"name": "following_id",
"tableFrom": "follow",
"tableTo": "account",
"schemaTo": "public",
"columnsFrom": [
"following"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"follow_pk": {
"name": "follow_pk",
"columns": [
"following",
"follower"
]
}
},
"uniqueConstraints": {},
"checkConstraints": {},
"policies": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {}
}
}

View File

@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1763702111620,
"tag": "0000_parallel_rocket_racer",
"breakpoints": true
}
]
}

74
drizzle/relations.ts Normal file
View File

@@ -0,0 +1,74 @@
import { relations } from "drizzle-orm/relations";
import { account, comment, schedule, participant, favorite, follow } from "./schema";
export const commentRelations = relations(comment, ({one, many}) => ({
account: one(account, {
fields: [comment.writerId],
references: [account.id]
}),
comment: one(comment, {
fields: [comment.parentId],
references: [comment.id],
relationName: "comment_parentId_comment_id"
}),
comments: many(comment, {
relationName: "comment_parentId_comment_id"
}),
}));
export const accountRelations = relations(account, ({many}) => ({
comments: many(comment),
schedules: many(schedule),
participants: many(participant),
favorites: many(favorite),
follows_follower: many(follow, {
relationName: "follow_follower_account_id"
}),
follows_following: many(follow, {
relationName: "follow_following_account_id"
}),
}));
export const scheduleRelations = relations(schedule, ({one, many}) => ({
account: one(account, {
fields: [schedule.owner],
references: [account.id]
}),
participants: many(participant),
favorites: many(favorite),
}));
export const participantRelations = relations(participant, ({one}) => ({
schedule: one(schedule, {
fields: [participant.scheduleId],
references: [schedule.id]
}),
account: one(account, {
fields: [participant.participantId],
references: [account.id]
}),
}));
export const favoriteRelations = relations(favorite, ({one}) => ({
schedule: one(schedule, {
fields: [favorite.scheduleId],
references: [schedule.id]
}),
account: one(account, {
fields: [favorite.userId],
references: [account.id]
}),
}));
export const followRelations = relations(follow, ({one}) => ({
account_follower: one(account, {
fields: [follow.follower],
references: [account.id],
relationName: "follow_follower_account_id"
}),
account_following: one(account, {
fields: [follow.following],
references: [account.id],
relationName: "follow_following_account_id"
}),
}));

120
drizzle/schema.ts Normal file
View File

@@ -0,0 +1,120 @@
import { pgTable, foreignKey, uuid, text, date, boolean, index, varchar, primaryKey } from "drizzle-orm/pg-core"
import { sql } from "drizzle-orm"
export const comment = pgTable("comment", {
id: uuid().primaryKey().notNull(),
content: text(),
createdAt: date("created_at"),
isDeleted: boolean("is_deleted").default(false),
writerId: uuid("writer_id"),
parentId: uuid("parent_id"),
}, (table) => [
foreignKey({
columns: [table.writerId],
foreignColumns: [account.id],
name: "writer_id"
}),
foreignKey({
columns: [table.parentId],
foreignColumns: [table.id],
name: "parent_id"
}),
]);
export const schedule = pgTable("schedule", {
id: uuid().primaryKey().notNull(),
name: varchar(),
startAt: date("start_at"),
endAt: date("end_at"),
status: varchar(),
content: text(),
isDeleted: boolean("is_deleted").default(false),
type: varchar(),
createdAt: date("created_at"),
owner: uuid(),
}, (table) => [
index("schedule_enddatetime_idx").using("btree", table.endAt.asc().nullsLast().op("date_ops")),
index("schedule_name_idx").using("btree", table.name.asc().nullsLast().op("text_ops"), table.content.asc().nullsLast().op("text_ops")),
index("schedule_startdatetime_idx").using("btree", table.startAt.asc().nullsLast().op("date_ops")),
index("schedule_status_idx").using("btree", table.status.asc().nullsLast().op("text_ops")),
index("schedule_type_idx").using("btree", table.type.asc().nullsLast().op("text_ops")),
foreignKey({
columns: [table.owner],
foreignColumns: [account.id],
name: "schedule_user_fk"
}),
]);
export const account = pgTable("account", {
name: varchar().notNull(),
email: varchar().notNull(),
password: varchar().notNull(),
birthday: date(),
accountId: varchar("account_id").notNull(),
nickname: varchar().notNull(),
status: varchar().default('wait').notNull(),
isDeleted: boolean("is_deleted").default(false).notNull(),
createdAt: date("created_at").defaultNow().notNull(),
id: uuid().default(sql`uuid_generate_v4()`).primaryKey().notNull(),
});
export const participant = pgTable("participant", {
participantId: uuid("participant_id").notNull(),
scheduleId: uuid("schedule_id").notNull(),
isDeleted: boolean("is_deleted").default(false),
}, (table) => [
index("participant_participant_id_idx").using("btree", table.participantId.asc().nullsLast().op("uuid_ops")),
index("participant_schedule_id_idx").using("btree", table.scheduleId.asc().nullsLast().op("uuid_ops")),
foreignKey({
columns: [table.scheduleId],
foreignColumns: [schedule.id],
name: "schedule_id"
}),
foreignKey({
columns: [table.participantId],
foreignColumns: [account.id],
name: "participant_id"
}),
]);
export const favorite = pgTable("favorite", {
isDeleted: boolean("is_deleted").default(false),
createdAt: date("created_at"),
userId: uuid("user_id").notNull(),
scheduleId: uuid("schedule_id").notNull(),
}, (table) => [
foreignKey({
columns: [table.scheduleId],
foreignColumns: [schedule.id],
name: "schedule_id"
}),
foreignKey({
columns: [table.userId],
foreignColumns: [account.id],
name: "user_id"
}),
primaryKey({ columns: [table.userId, table.scheduleId], name: "favorite_pk"}),
]);
export const follow = pgTable("follow", {
isDeleted: boolean("is_deleted").default(false),
isAccepted: boolean("is_accepted").default(false),
isLinked: boolean("is_linked").default(false),
createdAt: date("created_at"),
following: uuid().notNull(),
follower: uuid().notNull(),
}, (table) => [
foreignKey({
columns: [table.follower],
foreignColumns: [account.id],
name: "follower_id"
}),
foreignKey({
columns: [table.following],
foreignColumns: [account.id],
name: "following_id"
}),
primaryKey({ columns: [table.following, table.follower], name: "follow_pk"}),
]);

35
eslint.config.mjs Normal file
View File

@@ -0,0 +1,35 @@
// @ts-check
import eslint from '@eslint/js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import globals from 'globals';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{
ignores: ['eslint.config.mjs'],
},
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
eslintPluginPrettierRecommended,
{
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
sourceType: 'commonjs',
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
"prettier/prettier": ["error", { endOfLine: "auto" }],
},
},
);

8
nest-cli.json Normal file
View File

@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

View File

@@ -1,32 +1,80 @@
{ {
"name": "fastify", "name": "back",
"version": "1.0.0", "version": "0.0.1",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "ts-node src/server.ts",
"build": "tsc",
"start": "node dist/server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "", "description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": { "dependencies": {
"@fastify/swagger": "^9.6.1", "@nestjs/class-transformer": "^0.4.0",
"@fastify/swagger-ui": "^5.2.3", "@nestjs/class-validator": "^0.13.4",
"@nestjs/common": "^11.0.1",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"fastify": "^5.6.2", "drizzle-kit": "^0.31.7",
"fastify-postgres": "^3.6.0", "drizzle-orm": "^0.44.7",
"nodemailer": "^7.0.10", "ioredis": "^5.8.2",
"pg": "^8.16.3" "pg": "^8.16.3",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
}, },
"devDependencies": { "devDependencies": {
"@types/dotenv": "^6.1.1", "@eslint/eslintrc": "^3.2.0",
"@types/node": "^24.10.1", "@eslint/js": "^9.18.0",
"@types/nodemailer": "^7.0.3", "@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/express": "^5.0.0",
"@types/ioredis": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^22.10.7",
"@types/pg": "^8.15.6", "@types/pg": "^8.15.6",
"@types/supertest": "^6.0.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^16.0.0",
"jest": "^30.0.0",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.9.3" "tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
} }
} }

View File

@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});

12
src/app.controller.ts Normal file
View File

@@ -0,0 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}

12
src/app.module.ts Normal file
View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DbModule } from './db/db.module';
import { RedisModule } from './redis/redis.module';
@Module({
imports: [DbModule, RedisModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

8
src/app.service.ts Normal file
View File

@@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

View File

@@ -1,17 +0,0 @@
import Fastify from 'fastify';
import fastifyPostgres from 'fastify-postgres';
import { registerSwagger } from './utils/plugin/swagger';
import dotenv from 'dotenv';
dotenv.config();
export async function buildApp() {
const app = Fastify();
await app.register(fastifyPostgres, {
connectionString: `postgresql://${process.env.PGUSER}:${process.env.PGPASSWORD}@${process.env.PGHOST}:${process.env.PGPORT}/${process.env.PGDATABASE}`
})
await registerSwagger(app);
return app;
}

22
src/db/db.module.ts Normal file
View File

@@ -0,0 +1,22 @@
import { Global, Module } from "@nestjs/common";
import { Pool } from "pg";
import { drizzle, NodePgDatabase } from "drizzle-orm/node-postgres";
import * as schema from '../../drizzle/schema';
@Global()
@Module({
providers: [
{
provide: "DRIZZLE",
useFactory: (): NodePgDatabase<typeof schema> => {
const pool = new Pool({
connectionString: process.env.PG_DATABASE_URL
});
return drizzle(pool, { schema: schema });
}
}
],
exports: ["DRIZZLE"]
})
export class DbModule {}

8
src/main.ts Normal file
View File

@@ -0,0 +1,8 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

View File

@@ -0,0 +1,14 @@
import { Controller, Get, Post, Query } from "@nestjs/common";
import { CheckDuplicationRequestDto } from "./dto/checkDuplication/check-duplication-request.dto";
import { CheckDuplicationResponseDto } from "./dto/checkDuplication/check-duplication-response.dto";
import { AccountService } from "./account.service";
@Controller('account')
export class AccountController {
constructor(private readonly accountService: AccountService) {}
@Get('check-duplication')
async checkDuplication(@Query() query: CheckDuplicationRequestDto): Promise<CheckDuplicationResponseDto> {
return this.accountService.checkDuplication(query);
}
}

View File

@@ -0,0 +1,10 @@
import { Module } from "@nestjs/common";
import { AccountController } from "./account.controller";
import { AccountRepo } from "./account.repo";
import { AccountService } from "./account.service";
@Module({
controllers: [AccountController],
providers: [AccountService, AccountRepo]
})
export class AccountModule {}

View File

@@ -0,0 +1,21 @@
import { Inject, Injectable } from "@nestjs/common";
import * as schema from "drizzle/schema";
import { countDistinct, and, eq } from 'drizzle-orm';
import { NodePgDatabase } from "drizzle-orm/node-postgres";
@Injectable()
export class AccountRepo {
constructor(@Inject('DRIZZLE') private readonly db: NodePgDatabase<typeof schema>) {}
async checkDuplication(type: 'email' | 'accountId', value: string) {
const result = await this
.db
.select({ count: countDistinct(schema.account[type])})
.from(schema.account)
.where(
eq(schema.account[type], value)
);
return result[0].count;
}
}

View File

@@ -0,0 +1,15 @@
import { Injectable } from "@nestjs/common";
import { AccountRepo } from "./account.repo";
import { CheckDuplicationRequestDto } from "./dto/checkDuplication/check-duplication-request.dto";
import { CheckDuplicationResponseDto } from "./dto/checkDuplication/check-duplication-response.dto";
@Injectable()
export class AccountService {
constructor(private readonly accountRepo: AccountRepo) {}
async checkDuplication(data: CheckDuplicationRequestDto): Promise<CheckDuplicationResponseDto> {
const count = await this.accountRepo.checkDuplication(data.type, data.value);
return { isDuplicated: count > 0 };
}
}

View File

@@ -0,0 +1,9 @@
import { IsString, IsIn } from '@nestjs/class-validator'
export class CheckDuplicationRequestDto {
@IsIn(['email', 'accountId'])
type: 'email' | 'accountId';
@IsString()
value: string;
}

View File

@@ -0,0 +1,3 @@
export class CheckDuplicationResponseDto {
isDuplicated: boolean;
}

19
src/redis/redis.module.ts Normal file
View File

@@ -0,0 +1,19 @@
import { Global, Module } from "@nestjs/common";
import Redis from "ioredis";
@Global()
@Module({
providers: [
{
provide: "REDIS",
useFactory: () => {
return new Redis({
host: process.env.RD_HOST!,
port: Number(process.env.RD_PORT || 6779)
});
}
},
],
exports: ["REDIS"]
})
export class RedisModule{}

View File

@@ -1,14 +0,0 @@
import { buildApp } from './app';
const start = async () => {
try {
const app = await buildApp();
await app.listen({ port: 3000 });
console.log(`Server running on port ${process.env.PORT}`);
} catch (err) {
console.error(err);
process.exit(1);
}
}
start();

View File

@@ -1,33 +0,0 @@
import { FastifyInstance } from "fastify";
import fastifySwagger from "@fastify/swagger";
import fastifySwaggerUi from "@fastify/swagger-ui";
export async function registerSwagger(fastify: FastifyInstance) {
await fastify.register(fastifySwagger, {
openapi: {
info: {
title: "API Docs",
description: "API documentation for Fastify server",
version: "1.0.0"
},
servers: [
{
url: "http://localhost:3000",
description: "Local Development",
},
{
url: "https://api.scheduler.bkdhome.p-e.kr",
description: "Production Server"
}
]
}
});
await fastify.register(fastifySwaggerUi, {
routePrefix: "/docs",
uiConfig: {
docExpansion: "none",
deepLinking: false
}
});
}

25
test/app.e2e-spec.ts Normal file
View File

@@ -0,0 +1,25 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { App } from 'supertest/types';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication<App>;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

9
test/jest-e2e.json Normal file
View File

@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

4
tsconfig.build.json Normal file
View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

View File

@@ -1,19 +1,25 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "esnext", "module": "nodenext",
"lib": ["ESNext"], "moduleResolution": "nodenext",
"outDir": "dist", "resolvePackageJsonExports": true,
"rootDir": "src",
"strict": true,
"esModuleInterop": true, "esModuleInterop": true,
"module": "NodeNext", "isolatedModules": true,
"moduleResolution": "NodeNext", "declaration": true,
"resolveJsonModule": true, "removeComments": true,
"forceConsistentCasingInFileNames": true, "emitDecoratorMetadata": true,
"skipLibCheck": true, "experimentalDecorators": true,
// "noEmit": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
}, "target": "ES2023",
"include": ["src/**/*"], "sourceMap": true,
"exclude": ["node_modules", "dist"] "outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false
}
} }