Skip to content

Commit ecf78b1

Browse files
committed
.
0 parents  commit ecf78b1

37 files changed

+606
-0
lines changed

.dockerignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
**/*.bat
2+
**/*.md
3+
**/*.yml
4+
**/.dockerignore
5+
**/.editorconfig
6+
**/.git
7+
**/.gitattributes
8+
**/.github
9+
**/.gitignore
10+
**/.vs
11+
**/.vscode
12+
**/coverage
13+
**/dist
14+
**/dockerfile
15+
**/node_modules
16+
**/package-lock.json

.editorconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
indent_size = 4
6+
indent_style = space
7+
insert_final_newline = true
8+
trim_trailing_whitespace = true

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
JWT_KEY=D6DAE157BC16417DB79339E6FA439D10

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
* text=auto
2+
*.js diff=js
3+
*.ts diff=ts

.github/workflows/build.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: build
2+
on:
3+
push:
4+
branches: [main]
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- name: Checkout
10+
uses: actions/checkout@v4
11+
12+
- name: Node Setup
13+
uses: actions/setup-node@v4
14+
with:
15+
node-version: latest
16+
check-latest: true
17+
18+
- name: Node Build
19+
run: |
20+
npm run restore
21+
npm run build
22+
23+
- name: Artifact Upload
24+
uses: actions/upload-artifact@v4
25+
with:
26+
name: app
27+
path: dist

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*.bat
2+
.vscode
3+
dist
4+
node_modules
5+
package-lock.json

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

.prettierrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"bracketSpacing": true,
3+
"endOfLine": "lf",
4+
"printWidth": 125,
5+
"semi": true,
6+
"singleQuote": false,
7+
"tabWidth": 4,
8+
"trailingComma": "none"
9+
}

docker-compose.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# docker compose up --detach --build --force-recreate --remove-orphans
2+
3+
name: nest
4+
services:
5+
app:
6+
image: app
7+
container_name: app
8+
build:
9+
context: .
10+
dockerfile: dockerfile
11+
ports:
12+
- "4000:3000"

dockerfile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM node:alpine
2+
RUN npm install -g pnpm
3+
WORKDIR /app
4+
COPY package.json tsconfig.json ./
5+
RUN pnpm install
6+
COPY ./src ./src
7+
RUN npm run build
8+
EXPOSE 3000
9+
ENV NODE_ENV production
10+
CMD ["node", "dist/main.js"]

eslint.config.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import eslint from "@eslint/js";
2+
import tseslint from "typescript-eslint";
3+
import eslintConfigPrettier from "eslint-config-prettier";
4+
5+
export default tseslint.config(
6+
{
7+
ignores: ["**/*.mjs", "node_modules", "dist"],
8+
languageOptions: {
9+
parserOptions: {
10+
projectService: true,
11+
tsconfigRootDir: import.meta.dirname
12+
}
13+
}
14+
},
15+
eslint.configs.recommended,
16+
...tseslint.configs.strict,
17+
...tseslint.configs.stylistic,
18+
eslintConfigPrettier
19+
);

license.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
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:
2+
3+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
4+
5+
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.

nest-cli.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$schema": "https://json.schemastore.org/nest-cli",
3+
"collection": "@nestjs/schematics",
4+
"sourceRoot": "src",
5+
"compilerOptions": {
6+
"deleteOutDir": true,
7+
"plugins": [
8+
{
9+
"name": "@nestjs/swagger",
10+
"options": {
11+
"dtoFileNameSuffix": [".dto.ts"]
12+
}
13+
}
14+
]
15+
}
16+
}

package.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"name": "nest",
3+
"version": "1.0.0",
4+
"scripts": {
5+
"restore": "npm install",
6+
"check": "prettier src --config .prettierrc --check && eslint",
7+
"fix": "prettier src --config .prettierrc --write && eslint --fix",
8+
"start": "nest start --watch --debug",
9+
"test": "jest --no-cache",
10+
"build": "nest build"
11+
},
12+
"jest": {
13+
"testRegex": ".spec.ts",
14+
"transform": {
15+
".ts": "ts-jest"
16+
}
17+
},
18+
"dependencies": {
19+
"@nestjs/cache-manager": "2.3.0",
20+
"@nestjs/common": "10.4.12",
21+
"@nestjs/config": "3.3.0",
22+
"@nestjs/core": "10.4.12",
23+
"@nestjs/jwt": "10.2.0",
24+
"@nestjs/platform-express": "10.4.12",
25+
"@nestjs/swagger": "8.0.7",
26+
"class-validator": "0.14.1",
27+
"helmet": "8.0.0",
28+
"reflect-metadata": "0.2.2",
29+
"rxjs": "7.8.1",
30+
"swagger-ui-express": "5.0.1"
31+
},
32+
"devDependencies": {
33+
"@eslint/js": "9.16.0",
34+
"@nestjs/cli": "10.4.8",
35+
"@nestjs/schematics": "10.2.3",
36+
"@nestjs/testing": "10.4.12",
37+
"@types/express": "5.0.0",
38+
"@types/jest": "29.5.14",
39+
"@types/node": "22.10.1",
40+
"@types/supertest": "6.0.2",
41+
"eslint": "9.16.0",
42+
"eslint-config-prettier": "9.1.0",
43+
"jest": "29.7.0",
44+
"prettier": "3.4.1",
45+
"source-map-support": "0.5.21",
46+
"supertest": "7.0.0",
47+
"ts-jest": "29.2.5",
48+
"ts-loader": "9.5.1",
49+
"ts-node": "10.9.2",
50+
"tsconfig-paths": "4.2.0",
51+
"typescript": "5.7.2",
52+
"typescript-eslint": "8.16.0"
53+
}
54+
}

readme.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# NEST
2+
3+
API using Nest, JWT Authentication and Authorization, Swagger, Folder-by-Feature Structure.
4+
5+
## TECHNOLOGIES
6+
7+
* [Node](https://nodejs.org)
8+
* [Nest](https://nestjs.com)
9+
* [ESLint](https://eslint.org)
10+
* [Prettier](https://prettier.io)
11+
12+
## RUN
13+
14+
<details>
15+
<summary>Visual Studio Code</summary>
16+
17+
#### Prerequisites
18+
19+
* [Node](https://nodejs.org)
20+
* [Visual Studio Code](https://code.visualstudio.com)
21+
* [Visual Studio Code Node Debug](https://code.visualstudio.com/docs/nodejs/nodejs-debugging)
22+
23+
#### Steps
24+
25+
1. Run the command **npm run restore** in the **Terminal**.
26+
2. Run the command **npm run start** in the **Terminal**.
27+
3. Open **<http://localhost:3000>** in the **Web Browser**.
28+
29+
</details>
30+
31+
<details>
32+
<summary>Docker</summary>
33+
34+
#### Prerequisites
35+
36+
* [Docker](https://www.docker.com/get-started)
37+
38+
#### Steps
39+
40+
1. Run the command **docker compose up --detach --build --force-recreate --remove-orphans** in the **Terminal**.
41+
2. Open **<http://localhost:4000>** in the **Web Browser**.
42+
43+
</details>

src/app.module.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Module } from "@nestjs/common";
2+
import { ConfigModule } from "@nestjs/config";
3+
import { CacheModule } from "@nestjs/cache-manager";
4+
import { AuthModule } from "./auth/auth.module";
5+
import { UserModule } from "./user/user.module";
6+
import { ProductModule } from "./product/product.module";
7+
8+
@Module({
9+
imports: [
10+
ConfigModule.forRoot({ isGlobal: true, cache: true }),
11+
CacheModule.register(),
12+
AuthModule,
13+
UserModule,
14+
ProductModule
15+
]
16+
})
17+
export class AppModule {}

src/auth/anonymous.decorator.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { SetMetadata } from "@nestjs/common";
2+
export const ANONYMOUS = "ANONYMOUS";
3+
export const Anonymous = () => SetMetadata(ANONYMOUS, true);

src/auth/auth.controller.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Controller, Body, Post, HttpCode, HttpStatus } from "@nestjs/common";
2+
import { ApiTags } from "@nestjs/swagger";
3+
import { Anonymous } from "./anonymous.decorator";
4+
import { AuthService } from "./auth.service";
5+
import { AuthDto } from "./auth.dto";
6+
7+
@ApiTags("Auth")
8+
@Controller("auth")
9+
@Anonymous()
10+
export class AuthController {
11+
constructor(private readonly authService: AuthService) {}
12+
13+
@HttpCode(HttpStatus.OK)
14+
@Post()
15+
signIn(@Body() dto: AuthDto) {
16+
return this.authService.signIn(dto);
17+
}
18+
}

src/auth/auth.dto.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { IsNotEmpty } from "class-validator";
2+
3+
export class AuthDto {
4+
@IsNotEmpty()
5+
username!: string;
6+
7+
@IsNotEmpty()
8+
password!: string;
9+
}

src/auth/auth.guard.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Reflector } from "@nestjs/core";
2+
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from "@nestjs/common";
3+
import { JwtService } from "@nestjs/jwt";
4+
import { ANONYMOUS } from "./anonymous.decorator";
5+
6+
@Injectable()
7+
export class AuthGuard implements CanActivate {
8+
constructor(
9+
private readonly reflector: Reflector,
10+
private readonly jwtService: JwtService
11+
) {}
12+
13+
async canActivate(context: ExecutionContext): Promise<boolean> {
14+
if (this.reflector.getAllAndOverride<boolean>(ANONYMOUS, [context.getHandler(), context.getClass()])) return true;
15+
const request = context.switchToHttp().getRequest();
16+
const token = request.headers.authorization?.replace("Bearer", "").trim();
17+
18+
try {
19+
await this.jwtService.verifyAsync(token);
20+
} catch {
21+
throw new UnauthorizedException();
22+
}
23+
24+
return true;
25+
}
26+
}

src/auth/auth.module.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Module } from "@nestjs/common";
2+
import { AuthService } from "./auth.service";
3+
import { AuthController } from "./auth.controller";
4+
import { UserModule } from "src/user/user.module";
5+
import { JwtModule, JwtModuleOptions } from "@nestjs/jwt";
6+
import { ConfigService } from "@nestjs/config";
7+
import { APP_GUARD } from "@nestjs/core";
8+
import { AuthGuard } from "./auth.guard";
9+
10+
@Module({
11+
imports: [
12+
JwtModule.registerAsync({
13+
inject: [ConfigService],
14+
useFactory: async (configService: ConfigService): Promise<JwtModuleOptions> => ({
15+
secret: configService.get<string>("JWT_KEY")!
16+
})
17+
}),
18+
UserModule
19+
],
20+
providers: [
21+
{
22+
provide: APP_GUARD,
23+
useClass: AuthGuard
24+
},
25+
AuthService
26+
],
27+
controllers: [AuthController]
28+
})
29+
export class AuthModule {}

src/auth/auth.service.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Injectable, UnauthorizedException } from "@nestjs/common";
2+
import { JwtService } from "@nestjs/jwt";
3+
import { AuthDto } from "./auth.dto";
4+
import { UserService } from "src/user/user.service";
5+
6+
@Injectable()
7+
export class AuthService {
8+
constructor(
9+
private readonly jwtService: JwtService,
10+
private readonly userService: UserService
11+
) {}
12+
13+
async signIn(dto: AuthDto): Promise<string> {
14+
const user = await this.userService.getByUsername(dto.username);
15+
if (user?.password !== dto.password) throw new UnauthorizedException();
16+
return await this.jwtService.signAsync({ sub: user.id });
17+
}
18+
}

src/main.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { NestFactory } from "@nestjs/core";
2+
import { VersioningType, ValidationPipe } from "@nestjs/common";
3+
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
4+
import helmet from "helmet";
5+
import { AppModule } from "./app.module";
6+
7+
async function bootstrap() {
8+
const app = await NestFactory.create(AppModule);
9+
app.enableVersioning({ type: VersioningType.URI, defaultVersion: "1" });
10+
app.useGlobalPipes(new ValidationPipe({ transform: true }));
11+
app.use(helmet());
12+
SwaggerModule.setup(
13+
"",
14+
app,
15+
SwaggerModule.createDocument(app, new DocumentBuilder().setTitle("API").addBearerAuth().build())
16+
);
17+
await app.listen(3000);
18+
}
19+
20+
bootstrap();

src/product/dtos/add.product.dto.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { IsNotEmpty } from "class-validator";
2+
3+
export class AddProductDto {
4+
@IsNotEmpty()
5+
description!: string;
6+
}

0 commit comments

Comments
 (0)