diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..f082d90
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,16 @@
+**/*.bat
+**/*.md
+**/*.yml
+**/.dockerignore
+**/.editorconfig
+**/.git
+**/.gitattributes
+**/.github
+**/.gitignore
+**/.vs
+**/.vscode
+**/coverage
+**/dist
+**/dockerfile
+**/node_modules
+**/package-lock.json
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..de19759
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+root = true
+
+[*]
+charset = utf-8
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.env b/.env
new file mode 100644
index 0000000..365bc34
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+JWT_KEY=D6DAE157BC16417DB79339E6FA439D10
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..3332b89
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+* text=auto
+*.js diff=js
+*.ts diff=ts
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 0000000..d204dda
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,27 @@
+name: build
+on:
+ push:
+ branches: [main]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Node Setup
+ uses: actions/setup-node@v4
+ with:
+ node-version: latest
+ check-latest: true
+
+ - name: Node Build
+ run: |
+ npm run restore
+ npm run build
+
+ - name: Artifact Upload
+ uses: actions/upload-artifact@v4
+ with:
+ name: app
+ path: dist
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7f0ea82
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*.bat
+.vscode
+dist
+node_modules
+package-lock.json
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..9005ac9
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+package-lock=false
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..bb735f9
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,9 @@
+{
+ "bracketSpacing": true,
+ "endOfLine": "lf",
+ "printWidth": 125,
+ "semi": true,
+ "singleQuote": false,
+ "tabWidth": 4,
+ "trailingComma": "none"
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..ab0efe0
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,12 @@
+# docker compose up --detach --build --force-recreate --remove-orphans
+
+name: nest
+services:
+ app:
+ image: app
+ container_name: app
+ build:
+ context: .
+ dockerfile: dockerfile
+ ports:
+ - "4000:3000"
diff --git a/dockerfile b/dockerfile
new file mode 100644
index 0000000..5032e6c
--- /dev/null
+++ b/dockerfile
@@ -0,0 +1,10 @@
+FROM node:alpine
+RUN npm install -g pnpm
+WORKDIR /app
+COPY package.json tsconfig.json ./
+RUN pnpm install
+COPY ./src ./src
+RUN npm run build
+EXPOSE 3000
+ENV NODE_ENV production
+CMD ["node", "dist/main.js"]
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..d2f07cd
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,19 @@
+import eslint from "@eslint/js";
+import tseslint from "typescript-eslint";
+import eslintConfigPrettier from "eslint-config-prettier";
+
+export default tseslint.config(
+ {
+ ignores: ["**/*.mjs", "node_modules", "dist"],
+ languageOptions: {
+ parserOptions: {
+ projectService: true,
+ tsconfigRootDir: import.meta.dirname
+ }
+ }
+ },
+ eslint.configs.recommended,
+ ...tseslint.configs.strict,
+ ...tseslint.configs.stylistic,
+ eslintConfigPrettier
+);
diff --git a/license.md b/license.md
new file mode 100644
index 0000000..1f95d26
--- /dev/null
+++ b/license.md
@@ -0,0 +1,5 @@
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/nest-cli.json b/nest-cli.json
new file mode 100644
index 0000000..20942dc
--- /dev/null
+++ b/nest-cli.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "https://json.schemastore.org/nest-cli",
+ "collection": "@nestjs/schematics",
+ "sourceRoot": "src",
+ "compilerOptions": {
+ "deleteOutDir": true,
+ "plugins": [
+ {
+ "name": "@nestjs/swagger",
+ "options": {
+ "dtoFileNameSuffix": [".dto.ts"]
+ }
+ }
+ ]
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..5b4f1ee
--- /dev/null
+++ b/package.json
@@ -0,0 +1,54 @@
+{
+ "name": "nest",
+ "version": "1.0.0",
+ "scripts": {
+ "restore": "npm install",
+ "check": "prettier src --config .prettierrc --check && eslint",
+ "fix": "prettier src --config .prettierrc --write && eslint --fix",
+ "start": "nest start --watch --debug",
+ "test": "jest --no-cache",
+ "build": "nest build"
+ },
+ "jest": {
+ "testRegex": ".spec.ts",
+ "transform": {
+ ".ts": "ts-jest"
+ }
+ },
+ "dependencies": {
+ "@nestjs/cache-manager": "2.3.0",
+ "@nestjs/common": "10.4.12",
+ "@nestjs/config": "3.3.0",
+ "@nestjs/core": "10.4.12",
+ "@nestjs/jwt": "10.2.0",
+ "@nestjs/platform-express": "10.4.12",
+ "@nestjs/swagger": "8.0.7",
+ "class-validator": "0.14.1",
+ "helmet": "8.0.0",
+ "reflect-metadata": "0.2.2",
+ "rxjs": "7.8.1",
+ "swagger-ui-express": "5.0.1"
+ },
+ "devDependencies": {
+ "@eslint/js": "9.16.0",
+ "@nestjs/cli": "10.4.8",
+ "@nestjs/schematics": "10.2.3",
+ "@nestjs/testing": "10.4.12",
+ "@types/express": "5.0.0",
+ "@types/jest": "29.5.14",
+ "@types/node": "22.10.1",
+ "@types/supertest": "6.0.2",
+ "eslint": "9.16.0",
+ "eslint-config-prettier": "9.1.0",
+ "jest": "29.7.0",
+ "prettier": "3.4.1",
+ "source-map-support": "0.5.21",
+ "supertest": "7.0.0",
+ "ts-jest": "29.2.5",
+ "ts-loader": "9.5.1",
+ "ts-node": "10.9.2",
+ "tsconfig-paths": "4.2.0",
+ "typescript": "5.7.2",
+ "typescript-eslint": "8.16.0"
+ }
+}
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..4ec246e
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,43 @@
+# NEST
+
+API using Nest, JWT Authentication and Authorization, Swagger, Folder-by-Feature Structure.
+
+## TECHNOLOGIES
+
+* [Node](https://nodejs.org)
+* [Nest](https://nestjs.com)
+* [ESLint](https://eslint.org)
+* [Prettier](https://prettier.io)
+
+## RUN
+
+
+Visual Studio Code
+
+#### Prerequisites
+
+* [Node](https://nodejs.org)
+* [Visual Studio Code](https://code.visualstudio.com)
+* [Visual Studio Code Node Debug](https://code.visualstudio.com/docs/nodejs/nodejs-debugging)
+
+#### Steps
+
+1. Run the command **npm run restore** in the **Terminal**.
+2. Run the command **npm run start** in the **Terminal**.
+3. Open **** in the **Web Browser**.
+
+
+
+
+Docker
+
+#### Prerequisites
+
+* [Docker](https://www.docker.com/get-started)
+
+#### Steps
+
+1. Run the command **docker compose up --detach --build --force-recreate --remove-orphans** in the **Terminal**.
+2. Open **** in the **Web Browser**.
+
+
diff --git a/src/app.module.ts b/src/app.module.ts
new file mode 100644
index 0000000..c3d4f89
--- /dev/null
+++ b/src/app.module.ts
@@ -0,0 +1,17 @@
+import { Module } from "@nestjs/common";
+import { ConfigModule } from "@nestjs/config";
+import { CacheModule } from "@nestjs/cache-manager";
+import { AuthModule } from "./auth/auth.module";
+import { UserModule } from "./user/user.module";
+import { ProductModule } from "./product/product.module";
+
+@Module({
+ imports: [
+ ConfigModule.forRoot({ isGlobal: true, cache: true }),
+ CacheModule.register(),
+ AuthModule,
+ UserModule,
+ ProductModule
+ ]
+})
+export class AppModule {}
diff --git a/src/auth/anonymous.decorator.ts b/src/auth/anonymous.decorator.ts
new file mode 100644
index 0000000..33a3f22
--- /dev/null
+++ b/src/auth/anonymous.decorator.ts
@@ -0,0 +1,3 @@
+import { SetMetadata } from "@nestjs/common";
+export const ANONYMOUS = "ANONYMOUS";
+export const Anonymous = () => SetMetadata(ANONYMOUS, true);
diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts
new file mode 100644
index 0000000..d9eeee7
--- /dev/null
+++ b/src/auth/auth.controller.ts
@@ -0,0 +1,18 @@
+import { Controller, Body, Post, HttpCode, HttpStatus } from "@nestjs/common";
+import { ApiTags } from "@nestjs/swagger";
+import { Anonymous } from "./anonymous.decorator";
+import { AuthService } from "./auth.service";
+import { AuthDto } from "./auth.dto";
+
+@ApiTags("Auth")
+@Controller("auth")
+@Anonymous()
+export class AuthController {
+ constructor(private readonly authService: AuthService) {}
+
+ @HttpCode(HttpStatus.OK)
+ @Post()
+ signIn(@Body() dto: AuthDto) {
+ return this.authService.signIn(dto);
+ }
+}
diff --git a/src/auth/auth.dto.ts b/src/auth/auth.dto.ts
new file mode 100644
index 0000000..0a22a81
--- /dev/null
+++ b/src/auth/auth.dto.ts
@@ -0,0 +1,9 @@
+import { IsNotEmpty } from "class-validator";
+
+export class AuthDto {
+ @IsNotEmpty()
+ username!: string;
+
+ @IsNotEmpty()
+ password!: string;
+}
diff --git a/src/auth/auth.guard.ts b/src/auth/auth.guard.ts
new file mode 100644
index 0000000..5fb0756
--- /dev/null
+++ b/src/auth/auth.guard.ts
@@ -0,0 +1,26 @@
+import { Reflector } from "@nestjs/core";
+import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from "@nestjs/common";
+import { JwtService } from "@nestjs/jwt";
+import { ANONYMOUS } from "./anonymous.decorator";
+
+@Injectable()
+export class AuthGuard implements CanActivate {
+ constructor(
+ private readonly reflector: Reflector,
+ private readonly jwtService: JwtService
+ ) {}
+
+ async canActivate(context: ExecutionContext): Promise {
+ if (this.reflector.getAllAndOverride(ANONYMOUS, [context.getHandler(), context.getClass()])) return true;
+ const request = context.switchToHttp().getRequest();
+ const token = request.headers.authorization?.replace("Bearer", "").trim();
+
+ try {
+ await this.jwtService.verifyAsync(token);
+ } catch {
+ throw new UnauthorizedException();
+ }
+
+ return true;
+ }
+}
diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts
new file mode 100644
index 0000000..014d7b9
--- /dev/null
+++ b/src/auth/auth.module.ts
@@ -0,0 +1,29 @@
+import { Module } from "@nestjs/common";
+import { AuthService } from "./auth.service";
+import { AuthController } from "./auth.controller";
+import { UserModule } from "src/user/user.module";
+import { JwtModule, JwtModuleOptions } from "@nestjs/jwt";
+import { ConfigService } from "@nestjs/config";
+import { APP_GUARD } from "@nestjs/core";
+import { AuthGuard } from "./auth.guard";
+
+@Module({
+ imports: [
+ JwtModule.registerAsync({
+ inject: [ConfigService],
+ useFactory: async (configService: ConfigService): Promise => ({
+ secret: configService.get("JWT_KEY")!
+ })
+ }),
+ UserModule
+ ],
+ providers: [
+ {
+ provide: APP_GUARD,
+ useClass: AuthGuard
+ },
+ AuthService
+ ],
+ controllers: [AuthController]
+})
+export class AuthModule {}
diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts
new file mode 100644
index 0000000..45598a8
--- /dev/null
+++ b/src/auth/auth.service.ts
@@ -0,0 +1,18 @@
+import { Injectable, UnauthorizedException } from "@nestjs/common";
+import { JwtService } from "@nestjs/jwt";
+import { AuthDto } from "./auth.dto";
+import { UserService } from "src/user/user.service";
+
+@Injectable()
+export class AuthService {
+ constructor(
+ private readonly jwtService: JwtService,
+ private readonly userService: UserService
+ ) {}
+
+ async signIn(dto: AuthDto): Promise {
+ const user = await this.userService.getByUsername(dto.username);
+ if (user?.password !== dto.password) throw new UnauthorizedException();
+ return await this.jwtService.signAsync({ sub: user.id });
+ }
+}
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..e14b473
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,20 @@
+import { NestFactory } from "@nestjs/core";
+import { VersioningType, ValidationPipe } from "@nestjs/common";
+import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
+import helmet from "helmet";
+import { AppModule } from "./app.module";
+
+async function bootstrap() {
+ const app = await NestFactory.create(AppModule);
+ app.enableVersioning({ type: VersioningType.URI, defaultVersion: "1" });
+ app.useGlobalPipes(new ValidationPipe({ transform: true }));
+ app.use(helmet());
+ SwaggerModule.setup(
+ "",
+ app,
+ SwaggerModule.createDocument(app, new DocumentBuilder().setTitle("API").addBearerAuth().build())
+ );
+ await app.listen(3000);
+}
+
+bootstrap();
diff --git a/src/product/dtos/add.product.dto.ts b/src/product/dtos/add.product.dto.ts
new file mode 100644
index 0000000..a414751
--- /dev/null
+++ b/src/product/dtos/add.product.dto.ts
@@ -0,0 +1,6 @@
+import { IsNotEmpty } from "class-validator";
+
+export class AddProductDto {
+ @IsNotEmpty()
+ description!: string;
+}
diff --git a/src/product/dtos/list.product.dto.ts b/src/product/dtos/list.product.dto.ts
new file mode 100644
index 0000000..e3f45fa
--- /dev/null
+++ b/src/product/dtos/list.product.dto.ts
@@ -0,0 +1,3 @@
+export class ListProductDto {
+ description?: string;
+}
diff --git a/src/product/dtos/product.dto.ts b/src/product/dtos/product.dto.ts
new file mode 100644
index 0000000..20fd8cd
--- /dev/null
+++ b/src/product/dtos/product.dto.ts
@@ -0,0 +1,4 @@
+export class ProductDto {
+ id: string | undefined;
+ description: string | undefined;
+}
diff --git a/src/product/dtos/update.product.dto.ts b/src/product/dtos/update.product.dto.ts
new file mode 100644
index 0000000..195c1bf
--- /dev/null
+++ b/src/product/dtos/update.product.dto.ts
@@ -0,0 +1,11 @@
+import { ApiHideProperty } from "@nestjs/swagger";
+import { IsNotEmpty } from "class-validator";
+
+export class UpdateProductDto {
+ @ApiHideProperty()
+ @IsNotEmpty()
+ id!: string;
+
+ @IsNotEmpty()
+ description!: string;
+}
diff --git a/src/product/product.controller.spec.ts b/src/product/product.controller.spec.ts
new file mode 100644
index 0000000..dabde75
--- /dev/null
+++ b/src/product/product.controller.spec.ts
@@ -0,0 +1,21 @@
+import { TestingModule, Test } from "@nestjs/testing";
+import { ProductRepository } from "./product.repository";
+import { ProductService } from "./product.service";
+import { ProductController } from "./product.controller";
+
+describe("ProductController", () => {
+ let controller: ProductController;
+
+ beforeAll(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ controllers: [ProductController],
+ providers: [ProductRepository, ProductService]
+ }).compile();
+
+ controller = module.get(ProductController);
+ });
+
+ it("should be defined", () => {
+ expect(controller).toBeDefined();
+ });
+});
diff --git a/src/product/product.controller.ts b/src/product/product.controller.ts
new file mode 100644
index 0000000..da9b752
--- /dev/null
+++ b/src/product/product.controller.ts
@@ -0,0 +1,40 @@
+import { Controller, Param, Query, Body, Get, Post, Put, Delete, ParseUUIDPipe } from "@nestjs/common";
+import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
+import { ProductService } from "./product.service";
+import { AddProductDto } from "./dtos/add.product.dto";
+import { UpdateProductDto } from "./dtos/update.product.dto";
+import { ListProductDto } from "./dtos/list.product.dto";
+import { ProductDto } from "./dtos/product.dto";
+
+@ApiTags("Products")
+@ApiBearerAuth()
+@Controller("products")
+export class ProductController {
+ constructor(private readonly productService: ProductService) {}
+
+ @Post()
+ add(@Body() dto: AddProductDto): string {
+ return this.productService.add(dto);
+ }
+
+ @Delete(":id")
+ delete(@Param("id", new ParseUUIDPipe()) id: string): void {
+ return this.productService.delete(id);
+ }
+
+ @Get(":id")
+ get(@Param("id", new ParseUUIDPipe()) id: string): ProductDto | undefined {
+ return this.productService.get(id);
+ }
+
+ @Get()
+ list(@Query() dto: ListProductDto): ProductDto[] | undefined {
+ return this.productService.list(dto);
+ }
+
+ @Put(":id")
+ update(@Param("id", new ParseUUIDPipe()) id: string, @Body() dto: UpdateProductDto): void {
+ dto.id = id;
+ return this.productService.update(dto);
+ }
+}
diff --git a/src/product/product.entity.ts b/src/product/product.entity.ts
new file mode 100644
index 0000000..1342633
--- /dev/null
+++ b/src/product/product.entity.ts
@@ -0,0 +1,4 @@
+export class Product {
+ id!: string;
+ description!: string;
+}
diff --git a/src/product/product.module.ts b/src/product/product.module.ts
new file mode 100644
index 0000000..9b1a592
--- /dev/null
+++ b/src/product/product.module.ts
@@ -0,0 +1,10 @@
+import { Module } from "@nestjs/common";
+import { ProductController } from "./product.controller";
+import { ProductRepository } from "./product.repository";
+import { ProductService } from "./product.service";
+
+@Module({
+ controllers: [ProductController],
+ providers: [ProductRepository, ProductService]
+})
+export class ProductModule {}
diff --git a/src/product/product.repository.ts b/src/product/product.repository.ts
new file mode 100644
index 0000000..be0cd81
--- /dev/null
+++ b/src/product/product.repository.ts
@@ -0,0 +1,35 @@
+import { Injectable } from "@nestjs/common";
+import { Product } from "./product.entity";
+
+@Injectable()
+export class ProductRepository {
+ private products: Product[] = [
+ { id: crypto.randomUUID(), description: "Product 1" },
+ { id: crypto.randomUUID(), description: "Product 2" },
+ { id: crypto.randomUUID(), description: "Product 3" },
+ { id: crypto.randomUUID(), description: "Product 4" },
+ { id: crypto.randomUUID(), description: "Product 5" }
+ ];
+
+ add(product: Product): string {
+ product.id = crypto.randomUUID();
+ this.products.push(product);
+ return product.id;
+ }
+
+ delete(id: string): void {
+ this.products = this.products.filter((product) => product.id !== id);
+ }
+
+ get(id: string): Product | undefined {
+ return this.products.find((entity) => entity.id === id);
+ }
+
+ list(): Product[] | undefined {
+ return this.products;
+ }
+
+ update(product: Product): void {
+ this.products.some((entity, index) => entity.id === product.id && ((this.products[index] = product), true));
+ }
+}
diff --git a/src/product/product.service.ts b/src/product/product.service.ts
new file mode 100644
index 0000000..4bf882e
--- /dev/null
+++ b/src/product/product.service.ts
@@ -0,0 +1,32 @@
+import { Injectable } from "@nestjs/common";
+import { AddProductDto } from "./dtos/add.product.dto";
+import { UpdateProductDto } from "./dtos/update.product.dto";
+import { ListProductDto } from "./dtos/list.product.dto";
+import { ProductRepository } from "./product.repository";
+import { ProductDto } from "./dtos/product.dto";
+
+@Injectable()
+export class ProductService {
+ constructor(private readonly productRepository: ProductRepository) {}
+
+ add(dto: AddProductDto): string {
+ return this.productRepository.add({ ...dto, id: "" });
+ }
+
+ delete(id: string): void {
+ return this.productRepository.delete(id);
+ }
+
+ get(id: string): ProductDto | undefined {
+ return this.productRepository.get(id);
+ }
+
+ list(dto: ListProductDto): ProductDto[] | undefined {
+ console.log(dto);
+ return this.productRepository.list();
+ }
+
+ update(dto: UpdateProductDto): void {
+ return this.productRepository.update({ ...dto });
+ }
+}
diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts
new file mode 100644
index 0000000..c108add
--- /dev/null
+++ b/src/user/user.entity.ts
@@ -0,0 +1,5 @@
+export default interface User {
+ id: string;
+ username: string;
+ password: string;
+}
diff --git a/src/user/user.module.ts b/src/user/user.module.ts
new file mode 100644
index 0000000..5f2582c
--- /dev/null
+++ b/src/user/user.module.ts
@@ -0,0 +1,8 @@
+import { Module } from "@nestjs/common";
+import { UserService } from "./user.service";
+
+@Module({
+ providers: [UserService],
+ exports: [UserService]
+})
+export class UserModule {}
diff --git a/src/user/user.service.ts b/src/user/user.service.ts
new file mode 100644
index 0000000..f61c3b1
--- /dev/null
+++ b/src/user/user.service.ts
@@ -0,0 +1,17 @@
+import { Injectable } from "@nestjs/common";
+import User from "./user.entity";
+
+@Injectable()
+export class UserService {
+ private readonly users: User[] = [
+ {
+ id: crypto.randomUUID(),
+ username: "admin",
+ password: "123456"
+ }
+ ];
+
+ async getByUsername(username: string) {
+ return this.users.find((user) => user.username === username);
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..e8b24d3
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,41 @@
+{
+ "allowDefaultProject": true,
+ "compilerOptions": {
+ "allowSyntheticDefaultImports": true,
+ "allowUnreachableCode": false,
+ "allowUnusedLabels": false,
+ "alwaysStrict": true,
+ "baseUrl": ".",
+ "emitDecoratorMetadata": true,
+ "esModuleInterop": true,
+ "exactOptionalPropertyTypes": true,
+ "experimentalDecorators": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "CommonJS",
+ "moduleResolution": "Node",
+ "newLine": "lf",
+ "noErrorTruncation": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitAny": true,
+ "noImplicitOverride": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "noPropertyAccessFromIndexSignature": false,
+ "noUncheckedIndexedAccess": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "outDir": "dist",
+ "rootDir": "./src",
+ "skipDefaultLibCheck": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true,
+ "strictBindCallApply": true,
+ "strictFunctionTypes": true,
+ "strictNullChecks": true,
+ "strictPropertyInitialization": true,
+ "target": "ES2022",
+ "useUnknownInCatchVariables": true
+ },
+ "exclude": ["node_modules", "dist"]
+}