Skip to content

Commit b901e49

Browse files
authored
Merge pull request #37 from amosproj/9-landing-page-with-backup-data-table
9 landing page with backup data table
2 parents a5a6d14 + bf259ea commit b901e49

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+13933
-7243
lines changed

Documentation/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ basic setup:
55

66
- `npm ci`: dependency install
77
- copy `.env.example` file and rename to `.env` (adjust database properties according to database setup if necessary)
8+
- To insert dummy data into table backupData you can use the SQL script `dummyData.sql` in `apps/backend/src/app/utils`
89

910
running the code locally:
1011

apps/backend/src/app/app.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {AppService} from './app.service';
66
import {TypeOrmModule} from '@nestjs/typeorm';
77
import {DbConfigService} from "./db-config.service";
88
import {DemoModule} from "./demo/demo.module";
9+
import {BackupDataModule} from "./backupData/backupData.module";
910

1011
@Module({
1112
imports: [
@@ -16,7 +17,8 @@ import {DemoModule} from "./demo/demo.module";
1617
imports: [ConfigModule],
1718
useClass: DbConfigService,
1819
}),
19-
DemoModule
20+
DemoModule,
21+
BackupDataModule
2022
],
2123
controllers: [AppController],
2224
providers: [AppService],
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {Body, Controller, Get, Logger, NotFoundException, Param, Post, Query,} from '@nestjs/common';
2+
import {ApiCreatedResponse, ApiOkResponse, ApiOperation,} from '@nestjs/swagger';
3+
import {BackupDataService} from "./backupData.service";
4+
import {BackupDataDto} from "./dto/backupData.dto";
5+
import {CreateBackupDataDto} from "./dto/createBackupData.dto";
6+
import {PaginationDto} from "../utils/pagination/PaginationDto";
7+
import {PaginationOptionsDto} from "../utils/pagination/PaginationOptionsDto";
8+
import {BackupDataFilterDto} from "./dto/backupDataFilter.dto";
9+
import {BackupDataOrderOptionsDto} from "./dto/backupDataOrderOptions.dto";
10+
11+
@Controller('backupData')
12+
export class BackupDataController {
13+
readonly logger = new Logger(BackupDataController.name);
14+
15+
constructor(
16+
private readonly backupDataService: BackupDataService,
17+
) {
18+
}
19+
20+
@Get(":id")
21+
@ApiOperation({summary: 'Returns the backupData Object with the given id.'})
22+
@ApiOkResponse({type: BackupDataDto})
23+
async getById(
24+
@Param('id') id: string,
25+
): Promise<BackupDataDto> {
26+
const entity = await this.backupDataService.findOneById(id);
27+
if (!entity) {
28+
throw new NotFoundException();
29+
}
30+
31+
return entity;
32+
}
33+
34+
@Get()
35+
@ApiOperation({summary: 'Returns all backupData Objects.'})
36+
@ApiOkResponse()
37+
async findAll(@Query() paginationOptionsDto: PaginationOptionsDto, @Query() backupDataFilterDto: BackupDataFilterDto, @Query() backupDataOrderOptionsDto: BackupDataOrderOptionsDto): Promise<PaginationDto<BackupDataDto>> {
38+
return this.backupDataService.findAll(paginationOptionsDto, backupDataOrderOptionsDto, backupDataFilterDto);
39+
}
40+
41+
@Post()
42+
@ApiOperation({summary: 'Creates a new backupData Object.'})
43+
@ApiCreatedResponse({
44+
type: BackupDataDto,
45+
description: 'The created Backup Data Object.',
46+
})
47+
async create(
48+
@Body() createBackupDataDto: CreateBackupDataDto,
49+
): Promise<BackupDataDto> {
50+
return this.backupDataService.create(createBackupDataDto);
51+
}
52+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {Module} from '@nestjs/common';
2+
import {TypeOrmModule} from "@nestjs/typeorm";
3+
import {BackupDataService} from "./backupData.service";
4+
import {BackupDataController} from "./backupData.controller";
5+
import {BackupDataEntity} from "./entity/backupData.entity";
6+
7+
@Module({
8+
providers: [BackupDataService],
9+
imports: [
10+
TypeOrmModule.forFeature([BackupDataEntity]),
11+
],
12+
controllers: [BackupDataController],
13+
exports: [BackupDataService],
14+
})
15+
export class BackupDataModule {
16+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import {Test, TestingModule} from '@nestjs/testing';
2+
import {getRepositoryToken} from '@nestjs/typeorm';
3+
import {Between, ILike, Repository} from 'typeorm';
4+
import {BackupDataService} from './backupData.service';
5+
import {BackupDataEntity} from './entity/backupData.entity';
6+
import {BackupDataFilterDto} from "./dto/backupDataFilter.dto";
7+
import {BadRequestException} from "@nestjs/common";
8+
import {CreateBackupDataDto} from "./dto/createBackupData.dto";
9+
import {BackupDataOrderByOptions} from "./dto/backupDataOrderOptions.dto";
10+
import {SortOrder} from "../utils/pagination/SortOrder";
11+
12+
const mockBackupDataEntity: BackupDataEntity = {
13+
id: '123e4567-e89b-12d3-a456-426614174062',
14+
sizeMB: 100,
15+
creationDate: new Date('2023-12-30 00:00:00.000000'),
16+
bio: 'Test Bio',
17+
};
18+
19+
const mockBackupDataRepository = {
20+
findAndCount: jest.fn().mockResolvedValue([[mockBackupDataEntity], 1]),
21+
save: jest.fn().mockResolvedValue(mockBackupDataEntity),
22+
findOne: jest.fn().mockResolvedValue(mockBackupDataEntity),
23+
};
24+
25+
describe('BackupDataService', () => {
26+
let service: BackupDataService;
27+
let repository: Repository<BackupDataEntity>;
28+
29+
beforeEach(async () => {
30+
const module: TestingModule = await Test.createTestingModule({
31+
providers: [
32+
BackupDataService,
33+
{
34+
provide: getRepositoryToken(BackupDataEntity),
35+
useValue: mockBackupDataRepository,
36+
},
37+
],
38+
}).compile();
39+
40+
service = module.get(BackupDataService);
41+
repository = module.get(getRepositoryToken(BackupDataEntity));
42+
});
43+
44+
it('should be defined', () => {
45+
expect(service).toBeDefined();
46+
});
47+
describe('findAll', () => {
48+
it('should return paginated backup data with default sort by createdDate DESC', async () => {
49+
const result = await service.findAll({offset: 0, limit: 5}, {}, {});
50+
expect(result).toEqual({
51+
data: [mockBackupDataEntity],
52+
paginationData: {
53+
limit: 5,
54+
offset: 0,
55+
total: 1,
56+
}
57+
});
58+
expect(repository.findAndCount).toHaveBeenCalledWith({
59+
take: 5,
60+
order: {creationDate: 'DESC'},
61+
where: {},
62+
});
63+
});
64+
});
65+
66+
describe('createWhereClause', () => {
67+
it('should create a where clause for ID search', () => {
68+
const filterDto: BackupDataFilterDto = {id: '123'};
69+
const where = service.createWhereClause(filterDto);
70+
expect(where).toEqual({id: ILike('%123%')});
71+
});
72+
73+
it('should create a where clause for date range search', () => {
74+
const filterDto: BackupDataFilterDto = {fromDate: '2023-01-01', toDate: '2023-12-31'};
75+
const where = service.createWhereClause(filterDto);
76+
expect(where).toEqual({creationDate: Between(new Date('2023-01-01'), new Date('2023-12-31'))});
77+
});
78+
79+
it('should create a where clause for size range search', () => {
80+
const filterDto: BackupDataFilterDto = {fromSizeMB: 10, toSizeMB: 100};
81+
const where = service.createWhereClause(filterDto);
82+
expect(where).toEqual({sizeMB: Between(10, 100)});
83+
});
84+
85+
it('should create a where clause for bio search', () => {
86+
const filterDto: BackupDataFilterDto = {bio: 'test bio'};
87+
const where = service.createWhereClause(filterDto);
88+
expect(where).toEqual({bio: ILike('%test bio%')});
89+
});
90+
91+
it('should create a where clause for combined filters', () => {
92+
const filterDto: BackupDataFilterDto = {
93+
id: '123',
94+
fromDate: '2023-01-01',
95+
toDate: '2023-12-31',
96+
fromSizeMB: 10,
97+
toSizeMB: 100,
98+
bio: 'test bio',
99+
};
100+
const where = service.createWhereClause(filterDto);
101+
expect(where).toEqual({
102+
id: ILike('%123%'),
103+
creationDate: Between(new Date('2023-01-01'), new Date('2023-12-31')),
104+
sizeMB: Between(10, 100),
105+
bio: ILike('%test bio%'),
106+
});
107+
});
108+
109+
it('should throw an error for invalid fromDate', () => {
110+
const filterDto: BackupDataFilterDto = {fromDate: 'invalid-date'};
111+
expect(() => service.createWhereClause(filterDto)).toThrow(BadRequestException);
112+
});
113+
114+
it('should throw an error for invalid toDate', () => {
115+
const filterDto: BackupDataFilterDto = {toDate: 'invalid-date'};
116+
expect(() => service.createWhereClause(filterDto)).toThrow(BadRequestException);
117+
});
118+
});
119+
120+
describe('createOrderClause', () => {
121+
it('should create an order clause for creationDate in DESC order by default', () => {
122+
const orderClause = service.createOrderClause({});
123+
expect(orderClause).toEqual({creationDate: 'DESC'});
124+
});
125+
126+
it('should create an order clause for sizeMB in ASC order', () => {
127+
const orderClause = service.createOrderClause({
128+
orderBy: BackupDataOrderByOptions.SIZE,
129+
sortOrder: SortOrder.ASC
130+
});
131+
expect(orderClause).toEqual({sizeMB: 'ASC'});
132+
});
133+
134+
it('should create an order clause for bio in DESC order', () => {
135+
const orderClause = service.createOrderClause({
136+
orderBy: BackupDataOrderByOptions.BIO,
137+
sortOrder: SortOrder.DESC
138+
});
139+
expect(orderClause).toEqual({bio: 'DESC'});
140+
});
141+
142+
it('should create an order clause for id in ASC order', () => {
143+
const orderClause = service.createOrderClause({
144+
orderBy: BackupDataOrderByOptions.ID,
145+
sortOrder: SortOrder.ASC});
146+
expect(orderClause).toEqual({id: 'ASC'});
147+
});
148+
});
149+
150+
describe('create', () => {
151+
it('should create a new backup data entity', async () => {
152+
let createBackupDataDto: CreateBackupDataDto = new CreateBackupDataDto();
153+
Object.assign(createBackupDataDto, mockBackupDataEntity);
154+
155+
const result = await service.create(createBackupDataDto);
156+
expect(result).toEqual(mockBackupDataEntity);
157+
expect(repository.save).toHaveBeenCalledWith(mockBackupDataEntity);
158+
});
159+
});
160+
161+
describe('findOneById', () => {
162+
it('should return a backup data entity by id', async () => {
163+
const result = await service.findOneById(mockBackupDataEntity.id);
164+
expect(result).toEqual(mockBackupDataEntity);
165+
expect(repository.findOne).toHaveBeenCalledWith({where: {id: mockBackupDataEntity.id}});
166+
});
167+
});
168+
});
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import {BadRequestException, Injectable,} from '@nestjs/common';
2+
import {InjectRepository} from "@nestjs/typeorm";
3+
import {Between, ILike, LessThanOrEqual, MoreThanOrEqual, Repository} from "typeorm";
4+
import {BackupDataEntity} from "./entity/backupData.entity";
5+
import {CreateBackupDataDto} from "./dto/createBackupData.dto";
6+
import {PaginationOptionsDto} from "../utils/pagination/PaginationOptionsDto";
7+
import {PaginationDto} from "../utils/pagination/PaginationDto";
8+
import {PaginationService} from "../utils/pagination/paginationService";
9+
import {BackupDataDto} from "./dto/backupData.dto";
10+
import {BackupDataFilterDto} from "./dto/backupDataFilter.dto";
11+
import {BackupDataOrderOptionsDto} from "./dto/backupDataOrderOptions.dto";
12+
13+
@Injectable()
14+
export class BackupDataService extends PaginationService {
15+
constructor(
16+
@InjectRepository(BackupDataEntity)
17+
private backupDataRepository: Repository<BackupDataEntity>,
18+
) {
19+
super();
20+
}
21+
22+
/**
23+
* Find one Backup by id.
24+
* @param id
25+
*/
26+
async findOneById(id: string): Promise<BackupDataEntity | null> {
27+
return this.backupDataRepository.findOne({where: {id: id}});
28+
}
29+
30+
/**
31+
* Find all backups with pagination.
32+
*/
33+
async findAll(paginationOptionsDto: PaginationOptionsDto, backupDataOrderOptionsDto: BackupDataOrderOptionsDto, backupDataFilterDto: BackupDataFilterDto): Promise<PaginationDto<BackupDataDto>> {
34+
return await this.paginate<BackupDataEntity>(this.backupDataRepository, this.createOrderClause(backupDataOrderOptionsDto), this.createWhereClause(backupDataFilterDto), paginationOptionsDto);
35+
}
36+
37+
/**
38+
* Create a new backup data entity.
39+
* @param createBackupDataDto
40+
*/
41+
async create(createBackupDataDto: CreateBackupDataDto): Promise<BackupDataEntity> {
42+
const backupDataEntity = new BackupDataEntity();
43+
Object.assign(backupDataEntity, createBackupDataDto);
44+
45+
return this.backupDataRepository.save(backupDataEntity);
46+
}
47+
48+
/**
49+
* Create where clause.
50+
* @param backupDataFilterDto
51+
*/
52+
createWhereClause(backupDataFilterDto: BackupDataFilterDto) {
53+
let where: any = {};
54+
55+
//ID search
56+
if (backupDataFilterDto.id) {
57+
//like search
58+
where.id = ILike(`%${backupDataFilterDto.id}%`);
59+
}
60+
61+
// Check if params from and to are valid dates
62+
63+
let from: Date | null = null;
64+
let to: Date | null = null;
65+
if (backupDataFilterDto.fromDate) {
66+
from = new Date(backupDataFilterDto.fromDate);
67+
if (Number.isNaN(from.getTime())) {
68+
throw new BadRequestException('parameter fromDate is not a valid date');
69+
}
70+
}
71+
if (backupDataFilterDto.toDate) {
72+
to = new Date(backupDataFilterDto.toDate);
73+
if (Number.isNaN(to.getTime())) {
74+
throw new BadRequestException('parameter toDate is not a valid date');
75+
}
76+
}
77+
78+
//Creation date search
79+
if (backupDataFilterDto.fromDate && backupDataFilterDto.toDate) {
80+
where.creationDate = Between(from!, to!);
81+
} else if (backupDataFilterDto.fromDate) {
82+
where.creationDate = MoreThanOrEqual(from!);
83+
} else if (backupDataFilterDto.toDate) {
84+
where.creationDate = LessThanOrEqual(to!);
85+
}
86+
87+
//Size search
88+
if (backupDataFilterDto.fromSizeMB && backupDataFilterDto.toSizeMB) {
89+
where.sizeMB = Between(backupDataFilterDto.fromSizeMB, backupDataFilterDto.toSizeMB);
90+
} else if (backupDataFilterDto.fromSizeMB) {
91+
where.sizeMB = MoreThanOrEqual(backupDataFilterDto.fromSizeMB);
92+
} else if (backupDataFilterDto.toSizeMB) {
93+
where.sizeMB = LessThanOrEqual(backupDataFilterDto.toSizeMB);
94+
}
95+
96+
// Bio search
97+
if (backupDataFilterDto.bio) {
98+
where.bio = ILike(`%${backupDataFilterDto.bio}%`);
99+
}
100+
return where;
101+
}
102+
103+
/**
104+
* Create order clause.
105+
* @param backupDataOrderOptionsDto
106+
*/
107+
createOrderClause(backupDataOrderOptionsDto: BackupDataOrderOptionsDto) {
108+
return {
109+
[backupDataOrderOptionsDto.orderBy ?? 'creationDate']: backupDataOrderOptionsDto.sortOrder ?? 'DESC',
110+
}
111+
}
112+
}

0 commit comments

Comments
 (0)