Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
node_modules
node_modulescoverage/
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Stage 1: Build the Angular frontend
FROM node:22-alpine AS frontend-build
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm install --legacy-peer-deps
COPY frontend/ ./
RUN npm run build

# Stage 2: Build the NestJS backend
FROM node:22-alpine AS backend-build
WORKDIR /app/backend
COPY backend/package*.json ./
RUN npm install
COPY backend/ ./
RUN npm run build

# Stage 3: Production Backend Server (Node.js)
FROM node:22-alpine AS backend-prod
WORKDIR /app/backend
COPY backend/package*.json ./
RUN npm install --only=production
COPY --from=backend-build /app/backend/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main.js"]

# Stage 4: Production Frontend Server (Nginx)
FROM nginx:alpine AS frontend-prod
COPY --from=frontend-build /app/frontend/dist/mavluda-beauty/browser /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ The system consists of three main components:

```mermaid
graph TD
Client[Client Browser/Angular App] -->|HTTP/REST| API[NestJS Backend API]
API -->|Mongoose| MongoDB[(MongoDB Database)]
API -->|Redis Client| Redis[(Redis Cache)]
subgraph "Project Rebirth Architecture"
Client[Client Browser/Angular App] -->|HTTP/REST| API[App Server / NestJS Backend API]
API -->|Mongoose| MongoDB[(MongoDB Database)]
API -->|Redis Client| Redis[(Redis Cache)]
end
```

## Setup & Running
Expand Down
1 change: 1 addition & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
coverage/
6 changes: 5 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
"testEnvironment": "node",
"moduleNameMapper": {
"^@common/(.*)$": "<rootDir>/common/$1",
"^@modules/(.*)$": "<rootDir>/modules/$1"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AdminSettingsService } from './admin-settings.service';
import { AdminSettingsRepository } from '../infrastructure/repositories/admin-settings.repository';

describe('AdminSettingsService', () => {
let service: AdminSettingsService;
let repository: jest.Mocked<AdminSettingsRepository>;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AdminSettingsService,
{
provide: AdminSettingsRepository,
useValue: {
getSettings: jest.fn(),
updateSettings: jest.fn(),
},
},
],
}).compile();

service = module.get<AdminSettingsService>(AdminSettingsService);
repository = module.get(AdminSettingsRepository);
});

it('should getSettings', async () => {
repository.getSettings.mockResolvedValue({} as any);
await expect(service.getSettings()).resolves.toEqual({});
});

it('should updateSettings', async () => {
repository.updateSettings.mockResolvedValue({} as any);
await expect(service.updateSettings({} as any)).resolves.toEqual({});
});
});
53 changes: 53 additions & 0 deletions backend/src/modules/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
import { UserService } from '../user/application/user.service';
import { JwtService } from '@nestjs/jwt';

describe('AuthService', () => {
let service: AuthService;
let userService: jest.Mocked<UserService>;
let jwtService: jest.Mocked<JwtService>;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
{
provide: UserService,
useValue: {
findByEmail: jest.fn(),
create: jest.fn(),
},
},
{
provide: JwtService,
useValue: {
sign: jest.fn(),
},
},
],
}).compile();

service = module.get<AuthService>(AuthService);
userService = module.get(UserService);
jwtService = module.get(JwtService);
});

it('should validateUser', async () => {
userService.findByEmail.mockResolvedValue(null as any);
await expect(service.validateUser('test@test.com', 'pass')).resolves.toEqual(null);
});

it('should login', async () => {
jest.spyOn(service, 'validateUser').mockResolvedValue({ id: '1', email: 'test@test.com' } as any);
jwtService.sign.mockReturnValue('token');
await expect(service.login({ email: 'test@test.com', password: 'password' } as any)).resolves.toEqual({ access_token: 'token', refresh_token: 'token' });
});

it('should register', async () => {
userService.findByEmail.mockResolvedValue(null as any);
userService.create.mockResolvedValue({ id: '1', email: 'new@test.com' } as any);
jwtService.sign.mockReturnValue('token');
await expect(service.register({ email: 'new@test.com', password: 'password', firstName: 'New' } as any)).resolves.toEqual({ access_token: 'token', refresh_token: 'token' });
});
});
55 changes: 55 additions & 0 deletions backend/src/modules/auth/telegram-auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Test, TestingModule } from '@nestjs/testing';
import { TelegramAuthService } from './telegram-auth.service';
import { AppConfigService } from '../../common/config/app-config.service';
import { createHmac } from 'crypto';
import { UserService } from '@modules/user';

describe('TelegramAuthService', () => {
let service: TelegramAuthService;
let userService: jest.Mocked<UserService>;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
TelegramAuthService,
{
provide: AppConfigService,
useValue: {
telegramBotToken: 'test-token',
},
},
{
provide: UserService,
useValue: {
findOrCreate: jest.fn(),
},
},
],
}).compile();

service = module.get<TelegramAuthService>(TelegramAuthService);
userService = module.get(UserService);
});

it('should validate initData successfully', async () => {
const userJson = JSON.stringify({ id: 123, first_name: 'Test' });
const initDataString = `user=${userJson}&auth_date=1234567890`;

const secretKey = createHmac('sha256', 'WebAppData').update('test-token').digest();
const dataCheckString = `auth_date=1234567890\nuser=${userJson}`;
const hash = createHmac('sha256', secretKey).update(dataCheckString).digest('hex');

const initDataWithHash = `${initDataString}&hash=${hash}`;

userService.findOrCreate.mockResolvedValue({ id: '1', firstName: 'Test' } as any);

await expect(service.validateInitData(initDataWithHash)).resolves.toEqual({ id: '1', firstName: 'Test' });
});

it('should reject invalid initData hash', async () => {
const userJson = JSON.stringify({ id: 123, first_name: 'Test' });
const initDataString = `user=${userJson}&auth_date=1234567890&hash=invalidhash`;

await expect(service.validateInitData(initDataString)).rejects.toThrow();
});
});
60 changes: 60 additions & 0 deletions backend/src/modules/gallery/application/gallery.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Test, TestingModule } from '@nestjs/testing';
import { GalleryService } from './gallery.service';
import { GalleryRepository } from '../infrastructure/repositories/gallery.repository';

describe('GalleryService', () => {
let service: GalleryService;
let repository: jest.Mocked<GalleryRepository>;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
GalleryService,
{
provide: GalleryRepository,
useValue: {
findAll: jest.fn(),
findById: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
},
],
}).compile();

service = module.get<GalleryService>(GalleryService);
repository = module.get(GalleryRepository);
});

it('should findAll', async () => {
repository.findAll.mockResolvedValue([]);
await expect(service.findAll()).resolves.toEqual([]);
});

it('should findOne', async () => {
repository.findById.mockResolvedValue(null as any);
await expect(service.findOne('1')).rejects.toThrow();
repository.findById.mockResolvedValue({} as any);
await expect(service.findOne('1')).resolves.toEqual({});
});

it('should create', async () => {
repository.create.mockResolvedValue({} as any);
await expect(service.create({} as any)).resolves.toEqual({});
});

it('should update', async () => {
repository.update.mockResolvedValue(null as any);
await expect(service.update('1', {} as any)).rejects.toThrow();
repository.update.mockResolvedValue({} as any);
await expect(service.update('1', {} as any)).resolves.toEqual({});
});

it('should remove', async () => {
repository.delete.mockResolvedValue(null as any);
await expect(service.remove('1')).rejects.toThrow();
repository.delete.mockResolvedValue({} as any);
await expect(service.remove('1')).resolves.toBeUndefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Test, TestingModule } from '@nestjs/testing';
import { InventoryService } from './inventory.service';
import { InventoryRepository } from '../infrastructure/repositories/inventory.repository';

describe('InventoryService', () => {
let service: InventoryService;
let repository: jest.Mocked<InventoryRepository>;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
InventoryService,
{
provide: InventoryRepository,
useValue: {
findAll: jest.fn(),
findById: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
},
],
}).compile();

service = module.get<InventoryService>(InventoryService);
repository = module.get(InventoryRepository);
});

it('should findAll', async () => {
repository.findAll.mockResolvedValue([]);
await expect(service.findAll()).resolves.toEqual([]);
});

it('should findOne', async () => {
repository.findById.mockResolvedValue(null as any);
await expect(service.findOne('1')).rejects.toThrow();
repository.findById.mockResolvedValue({} as any);
await expect(service.findOne('1')).resolves.toEqual({});
});

it('should create', async () => {
repository.create.mockResolvedValue({} as any);
await expect(service.create({} as any)).resolves.toEqual({});
});

it('should update', async () => {
repository.update.mockResolvedValue(null as any);
await expect(service.update('1', {} as any)).rejects.toThrow();
repository.update.mockResolvedValue({} as any);
await expect(service.update('1', {} as any)).resolves.toEqual({});
});

it('should remove', async () => {
repository.delete.mockResolvedValue(null as any);
await expect(service.remove('1')).rejects.toThrow();
repository.delete.mockResolvedValue({} as any);
await expect(service.remove('1')).resolves.toBeUndefined();
});
});
Loading