Skip to content

Commit 12e1136

Browse files
feat: Add initial implementation for payment, gallery, user, authentication, and product management modules.
1 parent 900b5d5 commit 12e1136

33 files changed

Lines changed: 543 additions & 327 deletions
Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import { Body, Controller, Post, UnauthorizedException, Get, UseGuards, Request } from '@nestjs/common';
1+
import { Body, Controller, Post, UnauthorizedException } from '@nestjs/common';
22
import { TelegramAuthService } from './telegram-auth.service';
33
import { AuthService } from './auth.service';
44
import { LoginDto } from './dto/login.dto';
55
import { RegisterDto } from './dto/register.dto';
6-
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
76
import { Public } from '../common/decorators/public.decorator';
87

8+
import {
9+
AuthResponse,
10+
TelegramAuthResponse,
11+
} from './interfaces/auth-response.interface';
12+
913
@Controller('auth')
1014
export class AuthController {
1115
constructor(
@@ -15,26 +19,28 @@ export class AuthController {
1519

1620
@Public()
1721
@Post('login')
18-
async login(@Body() loginDto: LoginDto) {
22+
async login(@Body() loginDto: LoginDto): Promise<AuthResponse> {
1923
return this.authService.login(loginDto);
2024
}
2125

2226
@Public()
2327
@Post('register')
24-
async register(@Body() registerDto: RegisterDto) {
28+
async register(@Body() registerDto: RegisterDto): Promise<AuthResponse> {
2529
return this.authService.register(registerDto);
2630
}
2731

2832
@Post('telegram')
29-
async telegramAuth(@Body() body: { initData: string }) {
33+
async telegramAuth(
34+
@Body() body: { initData: string },
35+
): Promise<TelegramAuthResponse> {
3036
if (!body.initData) {
3137
throw new UnauthorizedException('No initData provided');
3238
}
3339
const user = await this.telegramAuthService.validateInitData(body.initData);
3440
// Use AuthService logic to return JWT
35-
const payload = { sub: user.id, role: user.role };
41+
// const payload = { sub: user.id, role: user.role };
3642
// Assuming we want to return token here too
3743
// return this.authService.login(user); (need user object with email/id)
38-
return { success: true, user };
44+
return { success: true, user };
3945
}
4046
}

backend/src/auth/auth.module.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ import { JwtStrategy } from './infrastructure/jwt.strategy';
1616
AppConfigModule,
1717
JwtModule.registerAsync({
1818
imports: [AppConfigModule],
19-
useFactory: (configService: AppConfigService) => ({
20-
secret: configService.jwtSecret,
21-
signOptions: { expiresIn: configService.jwtExpiresIn },
22-
} as any),
19+
useFactory: (configService: AppConfigService) =>
20+
({
21+
secret: configService.jwtSecret,
22+
signOptions: { expiresIn: configService.jwtExpiresIn },
23+
}) as any,
2324
inject: [AppConfigService],
2425
}),
2526
],

backend/src/auth/auth.service.ts

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,85 @@
1-
import { ConflictException, Injectable, UnauthorizedException } from '@nestjs/common';
1+
import {
2+
ConflictException,
3+
Injectable,
4+
UnauthorizedException,
5+
} from '@nestjs/common';
26
import { UserService } from '../user/application/user.service';
37
import { JwtService } from '@nestjs/jwt';
48
import * as bcrypt from 'bcrypt';
59
import { LoginDto } from './dto/login.dto';
610
import { RegisterDto } from './dto/register.dto';
711

12+
import { AuthResponse } from './interfaces/auth-response.interface';
13+
import { User } from '@user/domain/user.entity';
14+
815
@Injectable()
916
export class AuthService {
1017
constructor(
1118
private userService: UserService,
1219
private jwtService: JwtService,
1320
) {}
1421

15-
async validateUser(email: string, pass: string): Promise<any> {
22+
async validateUser(
23+
email: string,
24+
pass: string,
25+
): Promise<Omit<User, 'passwordHash'> | null> {
1626
const user = await this.userService.findByEmail(email);
1727
if (user && user.passwordHash) {
1828
const isMatch = await bcrypt.compare(pass, user.passwordHash);
1929
if (isMatch) {
20-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
21-
const { passwordHash, ...result } = user;
22-
return result;
30+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
31+
const { passwordHash, ...result } = user;
32+
return result;
2333
}
2434
}
2535
return null;
2636
}
2737

28-
async login(loginDto: LoginDto) {
38+
async login(loginDto: LoginDto): Promise<AuthResponse> {
2939
const user = await this.validateUser(loginDto.email, loginDto.password);
3040
if (!user) {
3141
throw new UnauthorizedException('Invalid credentials');
3242
}
33-
const payload = {
34-
email: user.email,
35-
sub: user.id,
43+
const payload = {
44+
email: user.email,
45+
sub: user.id,
3646
role: user.role,
3747
firstName: user.firstName,
3848
lastName: user.lastName,
39-
photoUrl: user.photoUrl
49+
photoUrl: user.photoUrl,
4050
};
4151
return {
4252
access_token: this.jwtService.sign(payload),
4353
};
4454
}
4555

46-
async register(registerDto: RegisterDto) {
56+
async register(registerDto: RegisterDto): Promise<AuthResponse> {
4757
const existing = await this.userService.findByEmail(registerDto.email);
4858
if (existing) {
49-
throw new ConflictException('User with this email already exists');
59+
throw new ConflictException('User with this email already exists');
5060
}
5161

5262
const salt = await bcrypt.genSalt();
5363
const passwordHash = await bcrypt.hash(registerDto.password, salt);
5464

5565
// Create user logic using UserService.create
5666
// UserService.create expects Omit<User, 'id' | 'createdAt'>
57-
// User constructor params: firstName, telegramId?, email?, passwordHash?, lastName?, username?, photoUrl?, role?
58-
// We strictly follow User type used in UserService
59-
6067
const newUser = await this.userService.create({
61-
firstName: registerDto.firstName,
62-
lastName: registerDto.lastName,
63-
email: registerDto.email,
64-
passwordHash: passwordHash,
65-
role: 'user',
66-
// Optional fields mapping
67-
username: registerDto.username,
68-
// Must explicitly undefined or omitting optional fields?
69-
// Typescript should allow missing optional fields if they are optional in type.
70-
} as any);
68+
firstName: registerDto.firstName,
69+
lastName: registerDto.lastName,
70+
email: registerDto.email,
71+
passwordHash: passwordHash,
72+
role: 'user',
73+
username: registerDto.username,
74+
} as unknown as Omit<User, 'id' | 'createdAt'>);
7175

72-
const payload = {
73-
email: newUser.email,
74-
sub: newUser.id,
76+
const payload = {
77+
email: newUser.email,
78+
sub: newUser.id,
7579
role: newUser.role,
7680
firstName: newUser.firstName,
7781
lastName: newUser.lastName,
78-
photoUrl: newUser.photoUrl
82+
photoUrl: newUser.photoUrl,
7983
};
8084
return {
8185
access_token: this.jwtService.sign(payload),

backend/src/auth/dto/register.dto.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { IsString, MinLength, MaxLength, IsNotEmpty, IsEmail, IsOptional } from 'class-validator';
1+
import {
2+
IsString,
3+
MinLength,
4+
MaxLength,
5+
IsNotEmpty,
6+
IsEmail,
7+
IsOptional,
8+
} from 'class-validator';
29

310
export class RegisterDto {
411
@IsNotEmpty()

backend/src/auth/infrastructure/jwt.strategy.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { PassportStrategy } from '@nestjs/passport';
33
import { Injectable, UnauthorizedException } from '@nestjs/common';
44
import { AppConfigService } from '../../common/config/app-config.service';
55

6+
import { JwtPayload } from '../interfaces/jwt-payload.interface';
7+
68
@Injectable()
79
export class JwtStrategy extends PassportStrategy(Strategy) {
810
constructor(private configService: AppConfigService) {
@@ -13,13 +15,13 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
1315
});
1416
}
1517

16-
async validate(payload: any) {
18+
validate(payload: JwtPayload) {
1719
// Payload contains the decoded JWT. We return the user object (or part of it)
1820
// which effectively validates the token signature and expiration.
1921
// The strictness of payload structure depends on what we sign.
2022
// Assuming { sub: userId, email: email, role: role }
2123
if (!payload.sub) {
22-
throw new UnauthorizedException();
24+
throw new UnauthorizedException();
2325
}
2426
return { userId: payload.sub, email: payload.email, role: payload.role };
2527
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { User } from '@user/domain/user.entity';
2+
3+
export interface AuthResponse {
4+
access_token: string;
5+
}
6+
7+
export interface TelegramAuthResponse {
8+
success: boolean;
9+
user: User;
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export interface JwtPayload {
2+
email: string;
3+
sub: string;
4+
role: string;
5+
firstName?: string;
6+
lastName?: string;
7+
photoUrl?: string;
8+
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import { ExecutionContext, Injectable } from '@nestjs/common';
32
import { Reflector } from '@nestjs/core';
43
import { AuthGuard } from '@nestjs/passport';

backend/src/common/guards/roles.guard.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
1+
import {
2+
Injectable,
3+
CanActivate,
4+
ExecutionContext,
5+
ForbiddenException,
6+
} from '@nestjs/common';
27
import { Reflector } from '@nestjs/core';
38
import { ROLES_KEY } from '../decorators/roles.decorator';
49

@@ -7,16 +12,16 @@ export class RolesGuard implements CanActivate {
712
constructor(private reflector: Reflector) {}
813

914
canActivate(context: ExecutionContext): boolean {
10-
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
11-
context.getHandler(),
12-
context.getClass(),
13-
]);
15+
const requiredRoles = this.reflector.getAllAndOverride<string[]>(
16+
ROLES_KEY,
17+
[context.getHandler(), context.getClass()],
18+
);
1419
if (!requiredRoles) {
1520
return true;
1621
}
1722
const { user } = context.switchToHttp().getRequest();
1823
if (!user || !user.role || !requiredRoles.includes(user.role)) {
19-
throw new ForbiddenException('Insufficient permissions');
24+
throw new ForbiddenException('Insufficient permissions');
2025
}
2126
return true;
2227
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Request } from 'express';
2+
3+
export interface AuthenticatedRequest extends Request {
4+
user?: {
5+
id: string;
6+
email: string;
7+
sub?: string;
8+
// Add other properties that are attached to the user object by your authentication strategy
9+
};
10+
}

0 commit comments

Comments
 (0)