Skip to content
This repository was archived by the owner on Jan 28, 2026. It is now read-only.

Commit 4727c69

Browse files
committed
feat: Refactor GitHub Actions workflow for Docker image build and transfer, add token validation endpoint in AuthController
1 parent 296dcdd commit 4727c69

6 files changed

Lines changed: 83 additions & 48 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,28 @@ env:
1111
IMAGE_NAME: ${{ secrets.DOCKER_IMAGE_NAME || 'atom-dbro-backend' }}
1212

1313
jobs:
14-
build:
15-
name: Build and Export Docker Image
14+
build-and-transfer:
15+
name: Build and Transfer Docker Image
1616
runs-on: ubuntu-latest
17+
if: github.ref == 'refs/heads/main'
1718

1819
steps:
1920
- name: Checkout code
2021
uses: actions/checkout@v4
2122

22-
- name: Set up Docker Buildx
23-
uses: docker/setup-buildx-action@v3
24-
23+
- name: Verify Docker access
24+
run: |
25+
echo "=== Verifying Docker access ==="
26+
docker ps 2>&1 | head -3 || {
27+
echo "❌ ERROR: Cannot access Docker daemon"
28+
exit 1
29+
}
30+
echo "✅ Docker access verified"
2531
- name: Build Docker image
26-
uses: docker/build-push-action@v5
27-
with:
28-
context: .
29-
file: ./Dockerfile
30-
push: false
31-
load: true
32-
tags: ${{ env.IMAGE_NAME }}:latest
33-
no-cache: true
34-
32+
run: |
33+
echo "📦 Building Docker image: ${{ env.IMAGE_NAME }}:latest"
34+
docker build --no-cache -t ${{ env.IMAGE_NAME }}:latest -f ./Dockerfile .
35+
echo "✅ Docker image built successfully"
3536
- name: Verify image was built
3637
run: |
3738
if ! docker images | grep -q "${{ env.IMAGE_NAME }}.*latest"; then
@@ -40,7 +41,6 @@ jobs:
4041
fi
4142
echo "✅ Docker image built successfully: ${{ env.IMAGE_NAME }}:latest"
4243
docker images | grep "${{ env.IMAGE_NAME }}"
43-
4444
- name: Export Docker image to tar.gz
4545
run: |
4646
echo "📦 Exporting Docker image to tar.gz..."
@@ -58,27 +58,6 @@ jobs:
5858
fi
5959
6060
echo "✅ Docker image exported successfully"
61-
62-
- name: Upload Docker image artifact
63-
uses: actions/upload-artifact@v4
64-
with:
65-
name: docker-image
66-
path: image.tar.gz
67-
retention-days: 1
68-
69-
transfer:
70-
name: Transfer Image to Server
71-
needs: build
72-
runs-on: ubuntu-latest
73-
if: github.ref == 'refs/heads/main'
74-
75-
steps:
76-
- name: Download Docker image artifact
77-
uses: actions/download-artifact@v4
78-
with:
79-
name: docker-image
80-
path: ./
81-
8261
- name: Set up SSH
8362
uses: webfactory/ssh-agent@v0.9.0
8463
with:
@@ -89,6 +68,8 @@ jobs:
8968
SSH_PORT="${DEPLOY_SSH_PORT:-22}"
9069
ssh -o StrictHostKeyChecking=no -p "$SSH_PORT" ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \
9170
"echo 'SSH connection successful' && hostname"
71+
env:
72+
DEPLOY_SSH_PORT: ${{ secrets.DEPLOY_SSH_PORT || '22' }}
9273

9374
- name: Transfer Docker image to server
9475
run: |
@@ -108,9 +89,17 @@ jobs:
10889
env:
10990
DEPLOY_SSH_PORT: ${{ secrets.DEPLOY_SSH_PORT || '22' }}
11091

92+
- name: Cleanup
93+
if: always()
94+
run: |
95+
echo "🧹 Cleaning up temporary files and Docker resources..."
96+
rm -f image.tar.gz
97+
docker rmi ${{ env.IMAGE_NAME }}:latest 2>/dev/null || true
98+
docker builder prune -f || true
99+
echo "✅ Cleanup completed"
111100
deploy:
112101
name: Deploy Application
113-
needs: transfer
102+
needs: build-and-transfer
114103
runs-on: ubuntu-latest
115104
if: github.ref == 'refs/heads/main'
116105

@@ -332,4 +321,4 @@ jobs:
332321
CONTAINER_NAME: ${{ secrets.DEPLOY_CONTAINER_NAME || 'atom-dbro-app' }}
333322
SERVICE_NAME: ${{ secrets.DEPLOY_SERVICE_NAME || 'app' }}
334323
IMAGE_NAME: ${{ env.IMAGE_NAME }}
335-
COMPOSE_PROJECT_NAME: ${{ secrets.DEPLOY_COMPOSE_PROJECT_NAME || '' }}
324+
COMPOSE_PROJECT_NAME: ${{ secrets.DEPLOY_COMPOSE_PROJECT_NAME || '' }}

src/auth/auth.controller.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { Controller, Post, Body, HttpCode, UseGuards, UseInterceptors } from '@nestjs/common';
2-
import { ApiTags, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger';
1+
import { Controller, Post, Body, HttpCode, UseGuards, UseInterceptors, Version } from '@nestjs/common';
2+
import { ApiTags, ApiOperation, ApiResponse, ApiBody, ApiBearerAuth } from '@nestjs/swagger';
33
import { AuthService } from './auth.service';
44
import { LoginDto, loginSchema, LoginDtoClass } from './dto/login.dto';
55
import { RefreshTokenDto, refreshTokenSchema, RefreshTokenDtoClass } from './dto/refresh-token.dto';
66
import { ZodValidation } from '../common/decorators/zod-validation.decorator';
77
import { Public } from './decorators/public.decorator';
88
import { RefreshTokenGuard } from './guards/refresh-token.guard';
99
import { LoginLoggingInterceptor } from './interceptors/login-logging.interceptor';
10+
import { JwtAuthGuard } from './guards/jwt-auth.guard';
11+
import { ValidateTokenGuard } from './guards/validate-token.guard';
1012

1113
@ApiTags('Авторизация')
1214
@Controller('auth')
@@ -34,5 +36,18 @@ export class AuthController {
3436
refresh(@Body() refreshTokenDto: RefreshTokenDto) {
3537
return this.authService.refresh(refreshTokenDto);
3638
}
39+
40+
@Post('validate')
41+
@Version('1')
42+
@UseGuards(ValidateTokenGuard)
43+
@HttpCode(200)
44+
@ApiBearerAuth()
45+
@ApiOperation({ summary: 'Валидация токена' })
46+
@ApiResponse({ status: 200, description: 'Токен валиден' })
47+
@ApiResponse({ status: 401, description: 'Токен не валиден' })
48+
@ApiResponse({ status: 400, description: 'Доступ разрешен только администраторам' })
49+
validate() {
50+
return { message: 'Токен валиден' };
51+
}
3752
}
3853

src/auth/auth.service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Injectable, UnauthorizedException, ConflictException, Logger } from '@nestjs/common';
1+
import { Injectable, UnauthorizedException, ConflictException, Logger, BadRequestException } from '@nestjs/common';
22
import { JwtService } from '@nestjs/jwt';
33
import { ConfigService } from '@nestjs/config';
44
import * as bcrypt from 'bcrypt';
@@ -48,7 +48,7 @@ export class AuthService {
4848

4949
// Проверяем, что пользователь имеет роль ADMIN
5050
if (user.role !== 'ADMIN') {
51-
throw new UnauthorizedException('Доступ разрешен только администраторам');
51+
throw new BadRequestException('Доступ разрешен только администраторам');
5252
}
5353

5454
const payload = { email: user.email, sub: user.id };
@@ -89,7 +89,7 @@ export class AuthService {
8989

9090
// Проверяем, что пользователь имеет роль ADMIN
9191
if (user.role !== 'ADMIN') {
92-
throw new UnauthorizedException('Доступ разрешен только администраторам');
92+
throw new BadRequestException('Доступ разрешен только администраторам');
9393
}
9494

9595
const newPayload = { email: user.email, sub: user.id };

src/auth/guards/jwt-auth.guard.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
1+
import { ExecutionContext, Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common';
22
import { Reflector } from '@nestjs/core';
33
import { AuthGuard } from '@nestjs/passport';
44
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
@@ -53,6 +53,11 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
5353
throw new UnauthorizedException(message);
5454
}
5555

56+
// Проверяем, что пользователь имеет роль ADMIN
57+
if (user.role !== 'ADMIN') {
58+
throw new BadRequestException('Доступ разрешен только администраторам');
59+
}
60+
5661
return user;
5762
}
5863
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ExecutionContext, Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common';
2+
import { AuthGuard } from '@nestjs/passport';
3+
4+
@Injectable()
5+
export class ValidateTokenGuard extends AuthGuard('jwt') {
6+
handleRequest(err: any, user: any, info: any, context: ExecutionContext) {
7+
// Если есть ошибка или пользователь отсутствует
8+
if (err || !user) {
9+
// Если ошибка уже является HttpException, пробрасываем её, но с нашим сообщением
10+
if (err && (err.statusCode || err instanceof UnauthorizedException)) {
11+
throw new UnauthorizedException('Токен не валиден');
12+
}
13+
14+
// Для всех остальных случаев невалидного токена
15+
throw new UnauthorizedException('Токен не валиден');
16+
}
17+
18+
// Проверяем, что пользователь имеет роль ADMIN
19+
if (user.role !== 'ADMIN') {
20+
throw new BadRequestException('Доступ разрешен только администраторам');
21+
}
22+
23+
return user;
24+
}
25+
}
26+

src/auth/strategies/jwt.strategy.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { Injectable, UnauthorizedException } from '@nestjs/common';
22
import { PassportStrategy } from '@nestjs/passport';
33
import { ExtractJwt, Strategy } from 'passport-jwt';
44
import { ConfigService } from '@nestjs/config';
5-
import { UserService } from '../../user/user.service';
5+
import { UserRepository } from '../../user/user.repository';
66

77
@Injectable()
88
export class JwtStrategy extends PassportStrategy(Strategy) {
99
constructor(
1010
private configService: ConfigService,
11-
private userService: UserService,
11+
private userRepository: UserRepository,
1212
) {
1313
super({
1414
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
@@ -19,11 +19,11 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
1919
}
2020

2121
async validate(payload: any) {
22-
const user = await this.userService.findOne(payload.sub);
22+
const user = await this.userRepository.findById(payload.sub);
2323
if (!user) {
2424
throw new UnauthorizedException();
2525
}
26-
return { userId: user.id, email: user.email };
26+
return { userId: user.id, email: user.email, role: user.role };
2727
}
2828
}
2929

0 commit comments

Comments
 (0)