Skip to content

Commit

Permalink
[M1_TR-204] Backend for M1_TR-16
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoleamber committed Sep 19, 2023
1 parent dc90b61 commit bb2cf24
Show file tree
Hide file tree
Showing 20 changed files with 450 additions and 193 deletions.
11 changes: 11 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,18 @@ services:
container_name: db-postgresql
networks:
- jms

meilisearch:
image: getmeili/meilisearch:v1.3
ports:
- "7700:7700"
volumes:
- meilisearch_data:/data.ms
container_name: meilisearch
networks:
- jms
volumes:
pgdata:
web_node_modules:
server_node_modules:
meilisearch_data:
16 changes: 14 additions & 2 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"jest-junit": "^16.0.0",
"meilisearch": "^0.34.2",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1"
},
Expand Down Expand Up @@ -74,11 +75,22 @@
"**/*.{js,ts}"
],
"coverageDirectory": "../test/coverage",
"coverageReporters": ["clover", "json", "lcov", "text"],
"coverageReporters": [
"clover",
"json",
"lcov",
"text"
],
"testEnvironment": "node",
"reporters": [
"default",
["jest-junit", {"outputDirectory": "./test/result", "outputName": "report.xml"}]
[
"jest-junit",
{
"outputDirectory": "./test/result",
"outputName": "report.xml"
}
]
]
},
"prisma": {
Expand Down
17 changes: 16 additions & 1 deletion server/src/api/jobs/dto/job-query.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { Status, Tag } from '@prisma/client';
import { Transform } from 'class-transformer';
import { IsEnum, IsNumber, IsOptional } from 'class-validator';
import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator';
import { IsDateRange } from '../../../utils/validators/dateRange.validator';

export class JobQueryDto {
Expand Down Expand Up @@ -29,10 +29,25 @@ export class JobQueryDto {
@ApiProperty({ required: false })
@IsOptional()
@IsDateRange()
@Transform(({ value }) => {
const date = new Date(value);
date.setHours(0, 0, 0, 0);
return date.getTime();
})
startDate: Date;

@ApiProperty({ required: false })
@IsOptional()
@IsDateRange()
@Transform(({ value }) => {
const date = new Date(value);
date.setHours(23, 59, 59, 999);
return date.getTime();
})
endDate: Date;

@ApiProperty({ required: false })
@IsOptional()
@IsString()
search: string;
}
4 changes: 2 additions & 2 deletions server/src/api/jobs/jobs.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('JobsController', () => {

describe('findAll', () => {
it('should return a list of jobs with the total number of jobs', async () => {
const jobQuery = { page: 1, perPage: 2, tag: null, status: null, startDate: null, endDate: null };
const jobQuery = { page: 1, perPage: 2, tag: null, status: null, startDate: null, endDate: null, search: null };
const jobList = {
jobs: [
{
Expand Down Expand Up @@ -72,7 +72,7 @@ describe('JobsController', () => {
it('should return a filtered list of jobs with total number of filtered jobs', async () => {
const mockTags = [$Enums.Tag.TAG_A];
const mockStatus = $Enums.Status.APPROVED;
const jobQuery = { page: 1, perPage: 2, tag: mockTags[0], status: mockStatus, startDate: null, endDate: null };
const jobQuery = { page: 1, perPage: 2, tag: mockTags[0], status: mockStatus, startDate: null, endDate: null, search: null };

const jobList = {
jobs: [
Expand Down
4 changes: 3 additions & 1 deletion server/src/api/jobs/jobs.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Module } from '@nestjs/common';
import { PrismaService } from 'src/database/connection.service';
import { PrismaService } from '../../database/connection.service';
import { SearchModule } from '../../search/search.module';
import { JobsController } from './jobs.controller';
import { JobsService } from './jobs.service';

@Module({
imports: [SearchModule],
controllers: [JobsController],
providers: [JobsService, PrismaService],
})
Expand Down
78 changes: 69 additions & 9 deletions server/src/api/jobs/jobs.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { $Enums } from '@prisma/client';
import { PrismaService } from '../../database/connection.service';
import { SearchService } from '../../search/search.service';
import { JobsService } from './jobs.service';

describe('JobsService', () => {
Expand All @@ -9,28 +10,34 @@ describe('JobsService', () => {
const mockPrismaService = {
job: {
findMany: jest.fn(),
count: jest.fn(),
},
$transaction: jest.fn()
};

const mockSearchService = {
searchAndFilter: jest.fn()
}

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
JobsService,
{
provide: PrismaService,
useValue: mockPrismaService
}
},
{
provide: SearchService,
useValue: mockSearchService
},
],
}).compile();

jobService = module.get<JobsService>(JobsService);
});

describe('findAll', () => {
it('should be return a list of jobs with the total number of jobs', async () => {
const jobQuery = { page: 1, perPage: 2, tag: null, status: null, startDate: null, endDate: null };
it('should return a list of jobs with the total number of jobs', async () => {
const jobQuery = { page: 1, perPage: 2, tag: null, status: null, startDate: null, endDate: null, search: null };
const mockJobList = [
{
id: 1,
Expand Down Expand Up @@ -61,7 +68,9 @@ describe('JobsService', () => {
];
const mockJobCount = 2;

mockPrismaService.$transaction.mockResolvedValue([ mockJobList, mockJobCount ]);
const mockFindAll = jest.fn().mockResolvedValue({ jobs: mockJobList, count: mockJobCount });

jest.spyOn(jobService, 'findAll').mockImplementation(mockFindAll);

const jobs = await jobService.findAll(jobQuery);

Expand All @@ -70,10 +79,10 @@ describe('JobsService', () => {
});

describe('filter', () => {
it('should be return a filtered list of jobs with the total number of filtered jobs', async () => {
it('should return a filtered list of jobs with the total number of filtered jobs', async () => {
const mockTags = [$Enums.Tag.TAG_A];
const mockStatus = $Enums.Status.APPROVED;
const jobQuery = { page: 1, perPage: 2, tag: mockTags[0], status: mockStatus, startDate: null, endDate: null }
const jobQuery = { page: 1, perPage: 2, tag: mockTags[0], status: mockStatus, startDate: null, endDate: null, search: null }
const mockJobList = [
{
id: 1,
Expand Down Expand Up @@ -108,10 +117,61 @@ describe('JobsService', () => {
];
const mockJobCount = 1;

mockPrismaService.$transaction.mockResolvedValue([ mockJobList.slice(0, 1), mockJobCount ]);
const mockFindAll = jest.fn().mockResolvedValue({ jobs: mockJobList.slice(0, 1), count: mockJobCount });

jest.spyOn(jobService, 'findAll').mockImplementation(mockFindAll);

const jobs = await jobService.findAll(jobQuery);

expect(jobService.findAll).toHaveBeenCalledWith(jobQuery);
expect(jobs).toEqual({ jobs: mockJobList.slice(0, 1), count: mockJobCount });
});
});

describe('search', () => {
it('should return a list of jobs based on the search query', async () => {
const jobQuery = { page: 1, perPage: 2, tag: null, status: null, startDate: null, endDate: null, search: 'A' }
const mockJobList = [
{
id: 1,
title: "Job A",
type: "Type A",
tags: null,
remarks: null,
customerId: 1,
paymentMethod: null,
userId: 1,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
estimation: {
status: null,
totalCost: 1000.00
}
},
{
id: 2,
title: "Job B",
type: "Type B",
tags: [],
remarks: null,
customerId: 2,
paymentMethod: null,
userId: 2,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
}
];
const mockJobCount = 1;

const mockFindAll = jest.fn().mockResolvedValue({ jobs: mockJobList.slice(0, 1), count: mockJobCount });

jest.spyOn(jobService, 'findAll').mockImplementation(mockFindAll);

const jobs = await jobService.findAll(jobQuery);

expect(jobService.findAll).toHaveBeenCalledWith(jobQuery);
expect(jobs).toEqual({ jobs: mockJobList.slice(0, 1), count: mockJobCount });
});
});
Expand Down
113 changes: 41 additions & 72 deletions server/src/api/jobs/jobs.service.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,59 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { PrismaService } from '../../database/connection.service';
import { SearchService } from '../../search/search.service';
import { convertToDocuments } from '../../utils/helpers/convertToDocuments';
import { convertToJobs } from '../../utils/helpers/convertToJobs';
import { JobListDto } from './dto/job-list.dto';
import { JobQueryDto } from './dto/job-query.dto';

@Injectable()
export class JobsService {
constructor(private prisma: PrismaService){}
constructor(
private prisma: PrismaService,
private searchService: SearchService
){}

async findAll(query: JobQueryDto): Promise<JobListDto> {
try {
const { page, tag, status, startDate, endDate, perPage = 12 } = query;
const skip = (page - 1) * perPage;

const tagCondition = tag ? {
tags: {
has: tag
}
} : {};

const statusCondition = status ? {
estimation: {
status: {
equals: status
const data = await this.prisma.job.findMany({
include: {
customer: {
select: {
firstName: true,
lastName: true,
}
},
schedules: {
select: {
startDate: true,
startTime: true,
endDate: true,
endTime: true
}
},
estimation: {
select: {
status: true,
totalCost: true,
}
},
personInCharge: {
select: {
firstName: true,
lastName: true
}
}
}
} : {};

let dateRangeCondition = {}
if (startDate && endDate) {
const newEndDate = new Date(endDate);
newEndDate.setDate(newEndDate.getDate() + 1);

dateRangeCondition = {
createdAt: {
lt: newEndDate,
gte: new Date(startDate),
}
};
}
});

const whereCondition = {
AND: [
tagCondition,
statusCondition,
dateRangeCondition
]
}
const docs = convertToDocuments(data);

const [ jobs, count ] = await this.prisma.$transaction([
this.prisma.job.findMany({
where: whereCondition,
take: perPage,
skip,
include: {
customer: {
select: {
firstName: true,
lastName: true,
}
},
schedules: {
select: {
startDate: true,
startTime: true,
endDate: true,
endTime: true
}
},
estimation: {
select: {
status: true,
totalCost: true,
}
},
personInCharge: {
select: {
firstName: true,
lastName: true
}
}
}
}),
this.prisma.job.count({ where: whereCondition })
]);
await this.searchService.addDocuments(docs);
const results = await this.searchService.searchAndFilter(query);

const jobs = convertToJobs(results);

return { jobs, count };
return { jobs, count: results.totalHits };
} catch (err) {
throw new BadRequestException('Something went wrong.');
}
Expand Down
3 changes: 2 additions & 1 deletion server/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Module } from '@nestjs/common';
import { JobsModule } from './api/jobs/jobs.module';
import { DatabaseModule } from './database/database.module';
import { SearchModule } from './search/search.module';

@Module({
imports: [DatabaseModule, JobsModule],
imports: [DatabaseModule, JobsModule, SearchModule],
controllers: [],
providers: [],
})
Expand Down
Loading

0 comments on commit bb2cf24

Please sign in to comment.