Skip to content

Commit

Permalink
Merge pull request #43 from w3bdesign/backend
Browse files Browse the repository at this point in the history
Backend services and tests
  • Loading branch information
w3bdesign authored Nov 20, 2024
2 parents 9f6a849 + e9e148a commit d7c8c75
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 85 deletions.
33 changes: 16 additions & 17 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,80 @@

## Customer-Facing Features

- User Registration and Authentication
- Implement user registration and login functionality.
- Ensure secure password storage and authentication mechanisms.

- Appointment Booking

- Develop the appointment booking interface.
- Implement logic to prevent double-booking conflicts.

- Service Selection

- Create a service catalog for customers to view and select services.

- Booking Management
- Allow customers to view, modify, or cancel their bookings.

- Notifications
- Implement email/SMS notifications for booking confirmations and reminders.
- Allow customers to view, modify, or cancel their bookings.

## Employee Features

- Employee Login

- Develop login functionality for employees.

- Schedule Management

- Create an interface for employees to view and manage their schedules.

- Availability Settings
- Allow employees to set their availability and block out times.

## Admin Features

- Admin Dashboard
- Develop a comprehensive admin dashboard for managing bookings, schedules, and salon performance.

- User Management

- Implement functionality to add, modify, or remove employee accounts.

- Reporting

- Create reporting tools to generate and export reports on bookings, revenue, and employee performance.

- Service Management
- Develop functionality to add or update services, including pricing and duration.

## Non-Functional Requirements
## Non-Functional Requirements (can be implemented in the future)

- Scalability

- Ensure the system can handle increased load and scale appropriately.

- Performance

- Optimize the system for fast load times and responsive interactions.

- Security

- Implement robust authentication and authorization mechanisms.
- Protect user data and ensure compliance with data protection regulations.

- Maintainability
- Write clean, well-documented code for future development.

- Reliability
- Ensure high availability and fault tolerance.

- Usability
- Design an intuitive user interface and user experience.
- Ensure high availability and fault tolerance.

## Deployment Plan

- Development Environment

- Set up local development environments with hot-reloading for rapid development.

- Testing/Staging Environment

- Mirror production environment for testing features before release.

- Production Environment

- Set up live environment with robust monitoring and backup strategies.

- CI/CD Pipeline

- Automate code building, testing, and deployment using CI/CD tools.

- Monitoring and Logging
Expand Down
26 changes: 26 additions & 0 deletions backend/src/database/migrations/1732141970009-ClearServices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class ClearServices1732141970009 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// First, delete all records from employee_services junction table
await queryRunner.query(`DELETE FROM "employee_services"`);

// Then, delete all records from services table
await queryRunner.query(`DELETE FROM "services"`);

// Insert new Norwegian services
await queryRunner.query(`
INSERT INTO "services" ("name", "description", "duration", "price", "isActive")
VALUES
('Standard Klipp', 'En standard og effektiv hårklipp for deg som har det travelt. Perfekt for å vedlikeholde din nåværende stil.', 20, 299.00, true),
('Styling Klipp', 'Komplett hårklipp og styling-service. Inkluderer konsultasjon for å finne det perfekte utseendet.', 30, 399.00, true),
('Skjegg Trim', 'Profesjonell skjeggtrimming og forming for å holde skjegget ditt velstelt.', 15, 199.00, true),
('Full Service', 'Komplett pakke som inkluderer hårklipp, skjeggtrim og styling. Vår premium-tjeneste.', 45, 549.00, true)
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
// Delete the Norwegian services
await queryRunner.query(`DELETE FROM "services" WHERE "name" IN ('Standard Klipp', 'Styling Klipp', 'Skjegg Trim', 'Full Service')`);
}
}
26 changes: 26 additions & 0 deletions backend/src/database/migrations/1732142680425-UpdateServices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class UpdateServices1732142680425 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// First, delete all records from employee_services junction table
await queryRunner.query(`DELETE FROM "employee_services"`);

// Then, delete all records from services table
await queryRunner.query(`DELETE FROM "services"`);

// Insert new Norwegian services
await queryRunner.query(`
INSERT INTO "services" ("name", "description", "duration", "price", "isActive")
VALUES
('Standard Klipp', 'En standard og effektiv hårklipp for deg som har det travelt. Perfekt for å vedlikeholde din nåværende stil.', 20, 299.00, true),
('Styling Klipp', 'Komplett hårklipp og styling-service. Inkluderer konsultasjon for å finne det perfekte utseendet.', 30, 399.00, true),
('Skjegg Trim', 'Profesjonell skjeggtrimming og forming for å holde skjegget ditt velstelt.', 15, 199.00, true),
('Full Service', 'Komplett pakke som inkluderer hårklipp, skjeggtrim og styling. Vår premium-tjeneste.', 45, 549.00, true)
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
// Delete the Norwegian services
await queryRunner.query(`DELETE FROM "services" WHERE "name" IN ('Standard Klipp', 'Styling Klipp', 'Skjegg Trim', 'Full Service')`);
}
}
16 changes: 9 additions & 7 deletions backend/src/database/seeds/create-initial-data.seed.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ describe('createInitialData', () => {

// Mock created services
const mockServices = [
{ id: '1', name: 'Haircut' },
{ id: '2', name: 'Hair Coloring' },
{ id: '3', name: 'Styling' },
{ id: '1', name: 'Standard Klipp' },
{ id: '2', name: 'Styling Klipp' },
{ id: '3', name: 'Skjegg Trim' },
{ id: '4', name: 'Full Service' },
];
(mockServiceRepository.save as jest.Mock).mockResolvedValue(mockServices);

Expand All @@ -103,9 +104,10 @@ describe('createInitialData', () => {

// Verify services were created
expect(mockServiceRepository.save).toHaveBeenCalledWith([
expect.objectContaining({ name: 'Haircut' }),
expect.objectContaining({ name: 'Hair Coloring' }),
expect.objectContaining({ name: 'Styling' }),
expect.objectContaining({ name: 'Standard Klipp' }),
expect.objectContaining({ name: 'Styling Klipp' }),
expect.objectContaining({ name: 'Skjegg Trim' }),
expect.objectContaining({ name: 'Full Service' }),
]);

// Verify employee user was created
Expand All @@ -117,7 +119,7 @@ describe('createInitialData', () => {
// Verify employee was created
expect(mockEmployeeRepository.save).toHaveBeenCalledWith(expect.objectContaining({
user: mockEmployeeUser,
specializations: ['haircut', 'coloring'],
specializations: ['klipp', 'styling', 'skjegg'],
}));

// Verify services were associated with employee
Expand Down
31 changes: 19 additions & 12 deletions backend/src/database/seeds/create-initial-data.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,33 @@ export const createInitialData = async (dataSource: DataSource) => {
// Create services
const services = await serviceRepository.save([
{
name: "Haircut",
description: "Basic haircut service",
price: 30.0,
name: "Standard Klipp",
description: "En standard og effektiv hårklipp for deg som har det travelt. Perfekt for å vedlikeholde din nåværende stil.",
price: 299.0,
duration: 20,
isActive: true,
},
{
name: "Styling Klipp",
description: "Komplett hårklipp og styling-service. Inkluderer konsultasjon for å finne det perfekte utseendet.",
price: 399.0,
duration: 30,
isActive: true,
},
{
name: "Hair Coloring",
description: "Professional hair coloring service",
price: 80.0,
duration: 120,
name: "Skjegg Trim",
description: "Profesjonell skjeggtrimming og forming for å holde skjegget ditt velstelt.",
price: 199.0,
duration: 15,
isActive: true,
},
{
name: "Styling",
description: "Hair styling service",
price: 40.0,
name: "Full Service",
description: "Komplett pakke som inkluderer hårklipp, skjeggtrim og styling. Vår premium-tjeneste.",
price: 549.0,
duration: 45,
isActive: true,
},
}
]);

// Check if employee user already exists
Expand All @@ -64,7 +71,7 @@ export const createInitialData = async (dataSource: DataSource) => {
// Create employee
const employee = await employeeRepository.save({
user: employeeUser,
specializations: ["haircut", "coloring"],
specializations: ["klipp", "styling", "skjegg"],
availability: {
monday: [{ start: "09:00", end: "17:00" }],
tuesday: [{ start: "09:00", end: "17:00" }],
Expand Down
46 changes: 46 additions & 0 deletions backend/src/database/seeds/create-services.seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { DataSource } from 'typeorm';
import { Seeder } from './seeder.interface';
import { Service } from '../../services/entities/service.entity';

export class CreateServicesSeed implements Seeder {
async run(dataSource: DataSource): Promise<void> {
const serviceRepository = dataSource.getRepository(Service);

const services = [
{
name: 'Standard Klipp',
description: 'En standard og effektiv hårklipp for deg som har det travelt. Perfekt for å vedlikeholde din nåværende stil.',
duration: 20,
price: 299,
},
{
name: 'Styling Klipp',
description: 'Komplett hårklipp og styling-service. Inkluderer konsultasjon for å finne det perfekte utseendet.',
duration: 30,
price: 399,
},
{
name: 'Skjegg Trim',
description: 'Profesjonell skjeggtrimming og forming for å holde skjegget ditt velstelt.',
duration: 15,
price: 199,
},
{
name: 'Full Service',
description: 'Komplett pakke som inkluderer hårklipp, skjeggtrim og styling. Vår premium-tjeneste.',
duration: 45,
price: 549,
},
];

for (const service of services) {
const existingService = await serviceRepository.findOne({
where: { name: service.name },
});

if (!existingService) {
await serviceRepository.save(service);
}
}
}
}
76 changes: 76 additions & 0 deletions backend/src/services/services.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ServicesController } from './services.controller';
import { ServicesService } from './services.service';
import { Service } from './entities/service.entity';
import { NotFoundException } from '@nestjs/common';

describe('ServicesController', () => {
let controller: ServicesController;
let service: ServicesService;

const mockService: Service = {
id: '1',
name: 'Standard Klipp',
description: 'En standard og effektiv hårklipp',
duration: 20,
price: 299,
isActive: true,
employees: [],
createdAt: new Date(),
updatedAt: new Date(),
};

const mockServicesService = {
findAll: jest.fn().mockResolvedValue([mockService]),
findOne: jest.fn().mockResolvedValue(mockService),
findByEmployee: jest.fn().mockResolvedValue([mockService]),
};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ServicesController],
providers: [
{
provide: ServicesService,
useValue: mockServicesService,
},
],
}).compile();

controller = module.get<ServicesController>(ServicesController);
service = module.get<ServicesService>(ServicesService);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});

describe('findAll', () => {
it('should return an array of services', async () => {
const result = await controller.findAll();
expect(result).toEqual([mockService]);
expect(service.findAll).toHaveBeenCalled();
});
});

describe('findOne', () => {
it('should return a service by id', async () => {
const result = await controller.findOne('1');
expect(result).toEqual(mockService);
expect(service.findOne).toHaveBeenCalledWith('1');
});

it('should throw NotFoundException when service is not found', async () => {
jest.spyOn(service, 'findOne').mockRejectedValueOnce(new NotFoundException());
await expect(controller.findOne('999')).rejects.toThrow(NotFoundException);
});
});

describe('findByEmployee', () => {
it('should return services by employee id', async () => {
const result = await controller.findByEmployee('1');
expect(result).toEqual([mockService]);
expect(service.findByEmployee).toHaveBeenCalledWith('1');
});
});
});
Loading

0 comments on commit d7c8c75

Please sign in to comment.