Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server
1 result
Show changes
Commits on Source (2)
Showing
with 769 additions and 128 deletions
## Description du problème
_Donnez une briève description du problème_
## L'environnement
#### Utilisez vous l'application sur :
- [ ] Mobile
- [ ] Ordinateur
##### En cas de mobile
###### Quel type de mobile utilisez-vous?
- [ ] Android
- [ ] Iphone
###### Quel navigateur utilisez-vous?
- [ ] Chrome
- [ ] Safari
- [ ] Autre
##### En cas d'ordinateur
###### Quel navigateur utilisez-vous?
- [ ] Chrome
- [ ] Firefox
- [ ] Safari
- [ ] Autre
## Le bug
#### Quelles sont les étapes qui ont menées au problème?
_Donnez une description des étapes, il est fortemment conseillé de l'accompagner par des captures d'écran_
#### Quel est le comportement obtenu?
_Donnez une description du comportement obtenu, il est fortemment conseillé de l'accompagner par des captures d'écran_
#### Quel est le comportement attendu?
_Donnez une description du comportement attendu_
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest [circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p> <p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center"> <p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a> <a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
...@@ -47,7 +47,6 @@ $ npm install ...@@ -47,7 +47,6 @@ $ npm install
### Base de donnée ### Base de donnée
```bash ```bash
$ docker-compose up -d database-ram $ docker-compose up -d database-ram
$ docker-compose up -d mongo-express $ docker-compose up -d mongo-express
...@@ -70,17 +69,20 @@ $ npm run start:prod ...@@ -70,17 +69,20 @@ $ npm run start:prod
```bash ```bash
# Lien vers le swagger # Lien vers le swagger
$ http://localhost:3000/api $ http://localhost:3000/doc
# Lien vers le mongo-express # Lien vers le mongo-express
$ http://localhost:8081 $ http://localhost:8081
``` ```
## Documentation ## Documentation
A documentation is generated with compodoc in addition of the existing documentation on the wiki. A documentation is generated with compodoc in addition of the existing documentation on the wiki.
```sh ```sh
npm run doc:serve npm run doc:serve
``` ```
You can now visualize it at : `localhost:8080` You can now visualize it at : `localhost:8080`
## Test ## Test
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
"release": "standard-version", "release": "standard-version",
"init-db": "node ./scripts/init-db.js", "init-db": "node ./scripts/init-db.js",
"test": "jest", "test": "jest",
"test:watch": "jest --config ./test/jest.json --watch", "test:watch": "jest --config ./test/jest.json --watch --coverage",
"test:cov": "jest --config ./test/jest.json --coverage --ci --reporters=default --reporters=jest-junit", "test:cov": "jest --config ./test/jest.json --coverage --ci --reporters=default --reporters=jest-junit",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e": "jest --config ./test/jest-e2e.json",
......
import { HttpModule } from '@nestjs/common'; import { HttpModule } from '@nestjs/common';
import { getModelToken } from '@nestjs/mongoose'; import { getModelToken } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { Types } from 'mongoose';
import { mockJwtAuthGuard } from '../../test/mock/guards/jwt-auth.mock.guard';
import { mockRoleGuard } from '../../test/mock/guards/role.mock.guard';
import { NewsletterServiceMock } from '../../test/mock/services/newsletter.mock.service';
import { StructuresServiceMock } from '../../test/mock/services/structures.mock.service';
import { UsersServiceMock } from '../../test/mock/services/user.mock.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { ConfigurationModule } from '../configuration/configuration.module'; import { ConfigurationModule } from '../configuration/configuration.module';
import { MailerService } from '../mailer/mailer.service'; import { MailerService } from '../mailer/mailer.service';
import { NewsletterSubscription } from '../newsletter/newsletter-subscription.schema'; import { NewsletterSubscription } from '../newsletter/newsletter-subscription.schema';
import { NewsletterService } from '../newsletter/newsletter.service'; import { NewsletterService } from '../newsletter/newsletter.service';
import { SearchModule } from '../search/search.module'; import { SearchModule } from '../search/search.module';
import { Structure } from '../structures/schemas/structure.schema'; import { Structure } from '../structures/schemas/structure.schema';
import { StructuresService } from '../structures/services/structures.service';
import { StructuresSearchService } from '../structures/services/structures-search.service'; import { StructuresSearchService } from '../structures/services/structures-search.service';
import { StructuresService } from '../structures/services/structures.service';
import { RolesGuard } from '../users/guards/roles.guard';
import { User } from '../users/schemas/user.schema'; import { User } from '../users/schemas/user.schema';
import { EmployerSearchService } from '../users/services/employer-search.service';
import { EmployerService } from '../users/services/employer.service';
import { JobsService } from '../users/services/jobs.service';
import { UsersService } from '../users/services/users.service'; import { UsersService } from '../users/services/users.service';
import { AdminController } from './admin.controller'; import { AdminController } from './admin.controller';
import { mockJwtAuthGuard } from '../../test/mock/guards/jwt-auth.mock.guard';
import { mockRoleGuard } from '../../test/mock/guards/role.mock.guard';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../users/guards/roles.guard';
import { UsersServiceMock } from '../../test/mock/services/user.mock.service';
import { StructuresServiceMock } from '../../test/mock/services/structures.mock.service';
import { NewsletterServiceMock } from '../../test/mock/services/newsletter.mock.service';
describe('AdminController', () => { describe('AdminController', () => {
let controller: AdminController; let controller: AdminController;
let userService: UsersService;
const mockEmployerSearchService = {
indexEmployer: jest.fn(),
search: jest.fn(),
dropIndex: jest.fn(),
createEmployerIndex: jest.fn(),
deleteIndex: jest.fn(),
};
const mockEmployerService = {
findOne: jest.fn(),
deleteOneId: jest.fn(),
findByName: jest.fn(),
findAllValidated: jest.fn(),
findAllUnvalidated: jest.fn(),
createEmployerFromAdmin: jest.fn(),
validate: jest.fn(),
update: jest.fn(),
mergeEmployer: jest.fn(),
deleteInvalidEmployer: jest.fn(),
};
const mockJobService = {
findOne: jest.fn(),
findByName: jest.fn(),
deleteOneId: jest.fn(),
findAll: jest.fn(),
findAllUnvalidated: jest.fn(),
createJobFromAdmin: jest.fn(),
validate: jest.fn(),
update: jest.fn(),
mergeJob: jest.fn(),
deleteInvalidJob: jest.fn(),
};
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
...@@ -41,6 +80,18 @@ describe('AdminController', () => { ...@@ -41,6 +80,18 @@ describe('AdminController', () => {
}, },
StructuresSearchService, StructuresSearchService,
MailerService, MailerService,
{
provide: EmployerService,
useValue: mockEmployerService,
},
{
provide: JobsService,
useValue: mockJobService,
},
{
provide: EmployerSearchService,
useValue: mockEmployerSearchService,
},
{ {
provide: getModelToken('User'), provide: getModelToken('User'),
useValue: User, useValue: User,
...@@ -63,6 +114,7 @@ describe('AdminController', () => { ...@@ -63,6 +114,7 @@ describe('AdminController', () => {
.compile(); .compile();
controller = module.get<AdminController>(AdminController); controller = module.get<AdminController>(AdminController);
userService = module.get<UsersService>(UsersService);
}); });
it('should be defined', () => { it('should be defined', () => {
...@@ -71,7 +123,7 @@ describe('AdminController', () => { ...@@ -71,7 +123,7 @@ describe('AdminController', () => {
it('should get pending attachments', async () => { it('should get pending attachments', async () => {
expect((await controller.getPendingAttachments()).length).toBe(2); expect((await controller.getPendingAttachments()).length).toBe(2);
expect(Object.keys((await controller.getPendingAttachments())[0]).length).toBe(3); expect(Object.keys((await controller.getPendingAttachments())[0]).length).toBe(4);
}); });
describe('Pending structures validation', () => { describe('Pending structures validation', () => {
...@@ -80,9 +132,10 @@ describe('AdminController', () => { ...@@ -80,9 +132,10 @@ describe('AdminController', () => {
structureId: '6093ba0e2ab5775cfc01ed3e', structureId: '6093ba0e2ab5775cfc01ed3e',
structureName: 'test', structureName: 'test',
userEmail: 'jean.paul@mii.com', userEmail: 'jean.paul@mii.com',
updatedAt: '2021-03-02T10:07:48.000Z',
}; };
expect((await controller.validatePendingStructure(pendingStructureTest)).length).toBe(2); expect((await controller.validatePendingStructure(pendingStructureTest)).length).toBe(2);
expect(Object.keys((await controller.validatePendingStructure(pendingStructureTest))[0]).length).toBe(3); expect(Object.keys((await controller.validatePendingStructure(pendingStructureTest))[0]).length).toBe(4);
}); });
it('should get structure does not exist', async () => { it('should get structure does not exist', async () => {
...@@ -90,6 +143,7 @@ describe('AdminController', () => { ...@@ -90,6 +143,7 @@ describe('AdminController', () => {
structureId: '1093ba0e2ab5775cfc01z2ki', structureId: '1093ba0e2ab5775cfc01z2ki',
structureName: 'test', structureName: 'test',
userEmail: 'jean.paul@mii.com', userEmail: 'jean.paul@mii.com',
updatedAt: '2021-03-02T10:07:48.000Z',
}; };
try { try {
await controller.validatePendingStructure(pendingStructureTest); await controller.validatePendingStructure(pendingStructureTest);
...@@ -106,9 +160,10 @@ describe('AdminController', () => { ...@@ -106,9 +160,10 @@ describe('AdminController', () => {
structureId: '6093ba0e2ab5775cfc01ed3e', structureId: '6093ba0e2ab5775cfc01ed3e',
structureName: 'test', structureName: 'test',
userEmail: 'jean.paul@mii.com', userEmail: 'jean.paul@mii.com',
updatedAt: '2021-03-02T10:07:48.000Z',
}; };
expect((await controller.refusePendingStructure(pendingStructureTest)).length).toBe(2); expect((await controller.refusePendingStructure(pendingStructureTest)).length).toBe(2);
expect(Object.keys((await controller.refusePendingStructure(pendingStructureTest))[0]).length).toBe(3); expect(Object.keys((await controller.refusePendingStructure(pendingStructureTest))[0]).length).toBe(4);
}); });
it('should get structure does not exist', async () => { it('should get structure does not exist', async () => {
...@@ -116,6 +171,7 @@ describe('AdminController', () => { ...@@ -116,6 +171,7 @@ describe('AdminController', () => {
structureId: '1093ba0e2ab5775cfc01z2ki', structureId: '1093ba0e2ab5775cfc01z2ki',
structureName: 'test', structureName: 'test',
userEmail: 'jean.paul@mii.com', userEmail: 'jean.paul@mii.com',
updatedAt: '2021-03-02T10:07:48.000Z',
}; };
try { try {
await controller.refusePendingStructure(pendingStructureTest); await controller.refusePendingStructure(pendingStructureTest);
...@@ -140,6 +196,134 @@ describe('AdminController', () => { ...@@ -140,6 +196,134 @@ describe('AdminController', () => {
}); });
}); });
describe('setUserEmployer', () => {
it('should set a new employer to the user', async () => {
const spyer = jest.spyOn(userService, 'updateUserEmployer');
const mockUserId = '6231aefe76598527c8d0b5bc';
const mockEmployer = {
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Sopra',
validated: true,
};
mockEmployerService.findOne.mockResolvedValueOnce(mockEmployer);
const reply = await controller.setUserEmployer({
userId: mockUserId,
employerId: String(mockEmployer._id),
});
expect(spyer).toBeCalledTimes(1);
expect(spyer).toBeCalledWith(mockUserId, mockEmployer);
expect(reply).toBeTruthy();
});
it('should not set a new employer to the user if the employer does not exist', async () => {
const spyer = jest.spyOn(userService, 'updateUserEmployer');
const mockUserId = '6231aefe76598527c8d0b5bc';
const mockEmployer = {
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Sopra',
validated: true,
};
mockEmployerService.findOne.mockResolvedValueOnce(null);
try {
await controller.setUserEmployer({
userId: mockUserId,
employerId: String(mockEmployer._id),
});
expect(true).toBe(false);
} catch (e) {
expect(spyer).toBeCalledTimes(0);
expect(e.message).toBe('Employer does not exist');
expect(e.status).toBe(400);
}
});
it('should not set a new employer to the user if the user does not exist', async () => {
const spyer = jest.spyOn(userService, 'updateUserEmployer');
const mockUserId = 'thisuserdoesnotexist';
const mockEmployer = {
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Sopra',
validated: true,
};
mockEmployerService.findOne.mockResolvedValueOnce(mockEmployer);
try {
await controller.setUserEmployer({
userId: mockUserId,
employerId: String(mockEmployer._id),
});
expect(true).toBe(false);
} catch (e) {
expect(spyer).toBeCalledTimes(0);
expect(e.message).toBe('User does not exist');
expect(e.status).toBe(400);
}
});
});
describe('setUserJob', () => {
it('should set a new job to the user', async () => {
const spyer = jest.spyOn(userService, 'updateUserJob');
const mockUserId = '6231aefe76598527c8d0b5bc';
const mockJob = {
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Toto',
validated: true,
};
mockJobService.findOne.mockResolvedValueOnce(mockJob);
const reply = await controller.setUserJob({
userId: mockUserId,
jobId: String(mockJob._id),
});
expect(spyer).toBeCalledTimes(1);
expect(spyer).toBeCalledWith(mockUserId, mockJob);
expect(reply).toBeTruthy();
});
it('should not set a new job to the user if the job does not exist', async () => {
const spyer = jest.spyOn(userService, 'updateUserJob');
const mockUserId = '6231aefe76598527c8d0b5bc';
const mockJob = {
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Dev',
validated: true,
};
mockJobService.findOne.mockResolvedValueOnce(null);
try {
await controller.setUserJob({
userId: mockUserId,
jobId: String(mockJob._id),
});
expect(true).toBe(false);
} catch (e) {
expect(spyer).toBeCalledTimes(0);
expect(e.message).toBe('Job does not exist');
expect(e.status).toBe(400);
}
});
it('should not set a new job to the user if the user does not exist', async () => {
const spyer = jest.spyOn(userService, 'updateUserJob');
const mockUserId = 'thisuserdoesntexist';
const mockJob = {
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Dev',
validated: true,
};
mockJobService.findOne.mockResolvedValueOnce(mockJob);
try {
await controller.setUserJob({
userId: mockUserId,
jobId: String(mockJob._id),
});
expect(true).toBe(false);
} catch (e) {
expect(spyer).toBeCalledTimes(0);
expect(e.message).toBe('User does not exist');
expect(e.status).toBe(400);
}
});
});
describe('Search user', () => { describe('Search user', () => {
it('should return all users, empty string', async () => { it('should return all users, empty string', async () => {
expect((await controller.searchUsers({ searchString: '' })).length).toBe(2); expect((await controller.searchUsers({ searchString: '' })).length).toBe(2);
...@@ -156,7 +340,7 @@ describe('AdminController', () => { ...@@ -156,7 +340,7 @@ describe('AdminController', () => {
}); });
}); });
describe('Search user newleetter subscription', () => { describe('Search user newsletter subscription', () => {
it('should return all subscribed users, empty string', async () => { it('should return all subscribed users, empty string', async () => {
expect((await controller.getNewsletterSubscriptions({ searchString: '' })).length).toBe(3); expect((await controller.getNewsletterSubscriptions({ searchString: '' })).length).toBe(3);
}); });
......
import { ApiOperation, ApiParam } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { import {
Body, Body,
Delete,
Param,
Controller, Controller,
Delete,
Get, Get,
Post,
UseGuards,
HttpStatus,
HttpException, HttpException,
HttpStatus,
Logger, Logger,
Param,
Post,
Put,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
import { validate } from 'class-validator';
import { DateTime, Interval } from 'luxon';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { NewsletterSubscription } from '../newsletter/newsletter-subscription.schema'; import { NewsletterSubscription } from '../newsletter/newsletter-subscription.schema';
import { NewsletterService } from '../newsletter/newsletter.service'; import { NewsletterService } from '../newsletter/newsletter.service';
import { Structure } from '../structures/schemas/structure.schema';
import { StructuresService } from '../structures/services/structures.service'; import { StructuresService } from '../structures/services/structures.service';
import { Roles } from '../users/decorators/roles.decorator'; import { Roles } from '../users/decorators/roles.decorator';
import { RolesGuard } from '../users/guards/roles.guard'; import { RolesGuard } from '../users/guards/roles.guard';
import { IUser } from '../users/interfaces/user.interface';
import { EmployerService } from '../users/services/employer.service';
import { JobsService } from '../users/services/jobs.service';
import { UsersService } from '../users/services/users.service'; import { UsersService } from '../users/services/users.service';
import { PendingStructureDto } from './dto/pending-structure.dto'; import { PendingStructureDto } from './dto/pending-structure.dto';
import { validate } from 'class-validator'; import { SetUserEmployerDto } from './dto/set-user-employer.dto';
import { Structure } from '../structures/schemas/structure.schema'; import { SetUserJobDto } from './dto/set-user-job.dto';
import { IUser } from '../users/interfaces/user.interface';
@Controller('admin') @Controller('admin')
export class AdminController { export class AdminController {
...@@ -29,18 +35,22 @@ export class AdminController { ...@@ -29,18 +35,22 @@ export class AdminController {
constructor( constructor(
private usersService: UsersService, private usersService: UsersService,
private structuresService: StructuresService, private structuresService: StructuresService,
private jobsService: JobsService,
private employerService: EmployerService,
private newsletterService: NewsletterService private newsletterService: NewsletterService
) {} ) {}
@UseGuards(JwtAuthGuard, RolesGuard) @UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin') @Roles('admin')
@Get('pendingStructures') @Get('pendingStructures')
@ApiOperation({ description: 'Get pending structre for validation' }) @ApiOperation({ description: 'Get pending structures for validation' })
public async getPendingAttachments(): Promise<PendingStructureDto[]> { public async getPendingAttachments(): Promise<PendingStructureDto[]> {
const pendingStructure = await this.usersService.getPendingStructures(); const pendingStructure = await this.usersService.getPendingStructures();
return Promise.all( return Promise.all(
pendingStructure.map(async (structure) => { pendingStructure.map(async (structure) => {
structure.structureName = (await this.structuresService.findOne(structure.structureId)).structureName; const structureDocument = await this.structuresService.findOne(structure.structureId);
structure.structureName = structureDocument.structureName;
structure.updatedAt = structureDocument.updatedAt;
return structure; return structure;
}) })
); );
...@@ -49,13 +59,30 @@ export class AdminController { ...@@ -49,13 +59,30 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard) @UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin') @Roles('admin')
@Get('adminStructuresList') @Get('adminStructuresList')
@ApiOperation({ description: 'Get pending structre for validation' }) @ApiOperation({ description: 'Get pending structures for validation' })
public async getAdminStructuresList(): Promise<any> { public async getAdminStructuresList(): Promise<any> {
const structuresList = { claimed: [], inClaim: [], toClaim: [], incomplete: [] }; const structuresList = { claimed: [], inClaim: [], toClaim: [], incomplete: [] };
structuresList.inClaim = await this.getPendingAttachments(); const today = DateTime.local().setZone('utc', { keepLocalTime: true });
structuresList.toClaim = (await this.structuresService.findAllUnclaimed()).filter( const inClaimStructures = await this.getPendingAttachments();
(demand) => !structuresList.inClaim.find((elem) => elem.structureId == demand.structureId) structuresList.inClaim = inClaimStructures.map((structure) => {
); return {
structureId: structure.structureId,
structureName: structure.structureName,
updatedAt: structure.updatedAt,
isOutdated: Interval.fromDateTimes(DateTime.fromISO(structure.updatedAt), today).length('months') > 6,
};
});
const toClaimStructures = await this.structuresService.findAllUnclaimed();
structuresList.toClaim = toClaimStructures
.filter((demand) => !structuresList.inClaim.find((elem) => elem.structureId == demand.structureId))
.map((structure) => {
return {
structureId: structure.structureId,
structureName: structure.structureName,
updatedAt: structure.updatedAt,
isOutdated: Interval.fromDateTimes(DateTime.fromISO(structure.updatedAt), today).length('months') > 6,
};
});
const allStructures = await this.structuresService.findAll(); const allStructures = await this.structuresService.findAll();
structuresList.claimed = allStructures structuresList.claimed = allStructures
.filter( .filter(
...@@ -64,14 +91,25 @@ export class AdminController { ...@@ -64,14 +91,25 @@ export class AdminController {
!structuresList.toClaim.find((elem) => elem.structureId == demand.id) !structuresList.toClaim.find((elem) => elem.structureId == demand.id)
) )
.map((structure) => { .map((structure) => {
return { structureId: structure.id, structureName: structure.structureName }; return {
structureId: structure.id,
structureName: structure.structureName,
updatedAt: structure.updatedAt,
isOutdated: Interval.fromDateTimes(DateTime.fromISO(structure.updatedAt), today).length('months') > 6,
};
}); });
structuresList.incomplete = await Promise.all( structuresList.incomplete = await Promise.all(
allStructures.map(async (struct) => { allStructures.map(async (struct) => {
const validity = await validate(new Structure(struct)); const validity = await validate(new Structure(struct));
if (validity.length > 0) { if (validity.length > 0) {
this.logger.debug(`getAdminStructuresList - validation failed. errors: ${validity.toString()}`); this.logger.debug(`getAdminStructuresList - validation failed. errors: ${validity.toString()}`);
return { structureId: struct.id, structureName: struct.structureName };
return {
structureId: struct.id,
structureName: struct.structureName,
updatedAt: struct.updatedAt,
isOutdated: Interval.fromDateTimes(DateTime.fromISO(struct.updatedAt), today).length('months') > 6,
};
} else { } else {
this.logger.debug('getAdminStructuresList - validation succeed'); this.logger.debug('getAdminStructuresList - validation succeed');
return null; return null;
...@@ -141,6 +179,7 @@ export class AdminController { ...@@ -141,6 +179,7 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard) @UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin') @Roles('admin')
@Delete('user/:id') @Delete('user/:id')
@ApiBearerAuth('JWT')
@ApiParam({ name: 'id', type: String, required: true }) @ApiParam({ name: 'id', type: String, required: true })
public async deleteUser(@Param() params) { public async deleteUser(@Param() params) {
const user = await this.usersService.deleteOneId(params.id); const user = await this.usersService.deleteOneId(params.id);
...@@ -156,6 +195,7 @@ export class AdminController { ...@@ -156,6 +195,7 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard) @UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin') @Roles('admin')
@ApiBearerAuth('JWT')
@Post('searchUsers') @Post('searchUsers')
public async searchUsers(@Body() searchString: { searchString: string }) { public async searchUsers(@Body() searchString: { searchString: string }) {
if (searchString && searchString.searchString && searchString.searchString.length > 0) if (searchString && searchString.searchString && searchString.searchString.length > 0)
...@@ -165,14 +205,28 @@ export class AdminController { ...@@ -165,14 +205,28 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard) @UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin') @Roles('admin')
@Get('getUnAttachedUsers') @ApiBearerAuth('JWT')
@Get('unAttachedUsers')
public async findUnattachedUsers() { public async findUnattachedUsers() {
return this.usersService.findAllUnattached(); return this.usersService.findAllUnattached().then((formatUsers) => {
return formatUsers.map((user) => {
return {
id: user._id,
surname: user.surname,
name: user.name,
email: user.email,
phone: user.phone,
job: user.job,
employer: user.employer,
};
});
});
} }
@UseGuards(JwtAuthGuard, RolesGuard) @UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin') @Roles('admin')
@Get('getAttachedUsers') @ApiBearerAuth('JWT')
@Get('attachedUsers')
public async findAttachedUsers() { public async findAttachedUsers() {
return this.usersService.findAllAttached().then(async (users: IUser[]) => { return this.usersService.findAllAttached().then(async (users: IUser[]) => {
return this.structuresService.getAllUserCompletedStructures(users); return this.structuresService.getAllUserCompletedStructures(users);
...@@ -180,8 +234,9 @@ export class AdminController { ...@@ -180,8 +234,9 @@ export class AdminController {
} }
@UseGuards(JwtAuthGuard, RolesGuard) @UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT')
@Roles('admin') @Roles('admin')
@Get('getUnVerifiedUsers') @Get('unVerifiedUsers')
public async findUnVerifiedUsers() { public async findUnVerifiedUsers() {
return this.usersService.findAllUnVerified().then(async (users: IUser[]) => { return this.usersService.findAllUnVerified().then(async (users: IUser[]) => {
return this.structuresService.getAllUserCompletedStructures(users); return this.structuresService.getAllUserCompletedStructures(users);
...@@ -190,6 +245,7 @@ export class AdminController { ...@@ -190,6 +245,7 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard) @UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin') @Roles('admin')
@ApiBearerAuth('JWT')
@Post('searchNewsletterSubscriptions') @Post('searchNewsletterSubscriptions')
public async getNewsletterSubscriptions(@Body() searchString: { searchString: string }) { public async getNewsletterSubscriptions(@Body() searchString: { searchString: string }) {
if (searchString && searchString.searchString && searchString.searchString.length > 0) if (searchString && searchString.searchString && searchString.searchString.length > 0)
...@@ -199,6 +255,7 @@ export class AdminController { ...@@ -199,6 +255,7 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard) @UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin') @Roles('admin')
@ApiBearerAuth('JWT')
@Get('countNewsletterSubscriptions') @Get('countNewsletterSubscriptions')
public async countNewsletterSubscriptions(): Promise<number> { public async countNewsletterSubscriptions(): Promise<number> {
return this.newsletterService.countNewsletterSubscriptions(); return this.newsletterService.countNewsletterSubscriptions();
...@@ -206,9 +263,60 @@ export class AdminController { ...@@ -206,9 +263,60 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard) @UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin') @Roles('admin')
@ApiBearerAuth('JWT')
@Delete('newsletterSubscription/:email') @Delete('newsletterSubscription/:email')
@ApiParam({ name: 'email', type: String, required: true }) @ApiParam({ name: 'email', type: String, required: true })
public async unsubscribeUserFromNewsletter(@Param() params): Promise<NewsletterSubscription> { public async unsubscribeUserFromNewsletter(@Param() params): Promise<NewsletterSubscription> {
return this.newsletterService.newsletterUnsubscribe(params.email); return this.newsletterService.newsletterUnsubscribe(params.email);
} }
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@ApiBearerAuth('JWT')
@ApiOperation({ description: 'Set user job' })
@ApiResponse({ status: HttpStatus.OK, description: 'Return user profile' })
@ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'User does not exist' })
@ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Job does not exist' })
@Put('setUserJob')
public async setUserJob(@Body() setUserJob: SetUserJobDto): Promise<IUser> {
this.logger.debug(`setUserJob`);
const jobDocument = await this.jobsService.findOne(setUserJob.jobId);
if (!jobDocument) {
this.logger.warn(`Job does not exist: ${setUserJob.jobId}`);
throw new HttpException('Job does not exist', HttpStatus.BAD_REQUEST);
}
const userDocument = await this.usersService.findById(setUserJob.userId);
if (!userDocument) {
this.logger.warn(`User does not exist: ${setUserJob.userId}`);
throw new HttpException('User does not exist', HttpStatus.BAD_REQUEST);
}
await this.usersService.updateUserJob(userDocument._id, jobDocument);
return this.usersService.findById(setUserJob.userId);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@ApiBearerAuth('JWT')
@ApiOperation({ description: 'Set user employer' })
@ApiResponse({ status: HttpStatus.OK, description: 'Return user profile' })
@ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'User does not exist' })
@ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Employer does not exist' })
@Put('setUserEmployer')
public async setUserEmployer(@Body() setUserEmployer: SetUserEmployerDto): Promise<IUser> {
this.logger.debug(`setUserEmployer`);
const employerDocument = await this.employerService.findOne(setUserEmployer.employerId);
if (!employerDocument) {
this.logger.warn(`Employer does not exist: ${setUserEmployer.employerId}`);
throw new HttpException('Employer does not exist', HttpStatus.BAD_REQUEST);
}
const userDocument = await this.usersService.findById(setUserEmployer.userId);
if (!userDocument) {
this.logger.warn(`User does not exist: ${setUserEmployer.userId}`);
throw new HttpException('User does not exist', HttpStatus.BAD_REQUEST);
}
await this.usersService.updateUserEmployer(userDocument._id, employerDocument);
return this.usersService.findById(setUserEmployer.userId);
}
} }
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';
export class MergeEmployerDto {
@IsNotEmpty()
@ApiProperty({ type: String })
sourceEmployerId: string;
@IsNotEmpty()
@ApiProperty({ type: String })
targetEmployerId: string;
}
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';
export class MergeJobDto {
@IsNotEmpty()
@ApiProperty({ type: String })
sourceJobId: string;
@IsNotEmpty()
@ApiProperty({ type: String })
targetJobId: string;
}
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsMongoId, IsNotEmpty, IsString } from 'class-validator'; import { IsDate, IsEmail, IsMongoId, IsNotEmpty, IsString } from 'class-validator';
export class PendingStructureDto { export class PendingStructureDto {
@IsNotEmpty() @IsNotEmpty()
...@@ -16,4 +16,9 @@ export class PendingStructureDto { ...@@ -16,4 +16,9 @@ export class PendingStructureDto {
@IsString() @IsString()
@ApiProperty({ type: String }) @ApiProperty({ type: String })
structureName: string; structureName: string;
@IsNotEmpty()
@IsDate()
@ApiProperty({ type: String })
updatedAt: string;
} }
import { IsNotEmpty } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class SetUserEmployerDto {
@IsNotEmpty()
@ApiProperty({ type: String })
userId: string;
@IsNotEmpty()
@ApiProperty({ type: String })
employerId: string;
}
import { IsNotEmpty } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class SetUserJobDto {
@IsNotEmpty()
@ApiProperty({ type: String })
userId: string;
@IsNotEmpty()
@ApiProperty({ type: String })
jobId: string;
}
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator'; import { IsDate, IsNotEmpty, IsString } from 'class-validator';
export class UnclaimedStructureDto { export class UnclaimedStructureDto {
@IsNotEmpty() @IsNotEmpty()
...@@ -11,4 +11,9 @@ export class UnclaimedStructureDto { ...@@ -11,4 +11,9 @@ export class UnclaimedStructureDto {
@IsString() @IsString()
@ApiProperty({ type: String }) @ApiProperty({ type: String })
structureName: string; structureName: string;
@IsNotEmpty()
@IsDate()
@ApiProperty({ type: String })
updatedAt: string;
} }
...@@ -61,6 +61,14 @@ export const config = { ...@@ -61,6 +61,14 @@ export const config = {
ejs: 'structureDeletionNotification.ejs', ejs: 'structureDeletionNotification.ejs',
json: 'structureDeletionNotification.json', json: 'structureDeletionNotification.json',
}, },
adminJobCreate: {
ejs: 'adminJobCreate.ejs',
json: 'adminJobCreate.json',
},
adminEmployerCreate: {
ejs: 'adminEmployerCreate.ejs',
json: 'adminEmployerCreate.json',
},
contactMessage: { contactMessage: {
ejs: 'contactMessage.ejs', ejs: 'contactMessage.ejs',
json: 'contactMessage.json', json: 'contactMessage.json',
......
Bonjour,<br />
<br />
Un nouvel employeur <%= employerName %> vient d'être créé,
<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/admin>"
>cliquez ici pour le valider.</a
>
{
"subject": "Nouvelle création d'employeur"
}
Bonjour,<br />
<br />
Une nouvelle fonction <%= jobName %> vient d'être créée,
<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/admin>"
>cliquez ici pour la valider.</a
>
{
"subject": "Nouvelle création de fonction"
}
import { ConfigurationService } from '../configuration/configuration.service'; import { ConfigurationService } from '../configuration/configuration.service';
import { Page } from '../pages/schemas/page.schema'; import { Page } from '../pages/schemas/page.schema';
import { Post } from '../posts/schemas/post.schema'; import { Post } from '../posts/schemas/post.schema';
import { UserRole } from '../users/enum/user-role.enum';
import { User } from '../users/schemas/user.schema';
export function rewriteGhostImgUrl(configService: ConfigurationService, itemData: Page | Post): Page | Post { export function rewriteGhostImgUrl(configService: ConfigurationService, itemData: Page | Post): Page | Post {
// Handle image display. Rewrite image URL to fit ghost infra issue. // Handle image display. Rewrite image URL to fit ghost infra issue.
...@@ -15,3 +17,7 @@ export function rewriteGhostImgUrl(configService: ConfigurationService, itemData ...@@ -15,3 +17,7 @@ export function rewriteGhostImgUrl(configService: ConfigurationService, itemData
} }
return itemData; return itemData;
} }
export function hasAdminRole(user: User): boolean {
return user.role === UserRole.admin;
}
...@@ -240,7 +240,11 @@ export class StructuresService { ...@@ -240,7 +240,11 @@ export class StructuresService {
await Promise.all( await Promise.all(
structures.map(async (structure: StructureDocument) => { structures.map(async (structure: StructureDocument) => {
if (!(await this.userService.isStructureClaimed(structure.id))) { if (!(await this.userService.isStructureClaimed(structure.id))) {
unclaimedStructures.push({ structureId: structure.id, structureName: structure.structureName }); unclaimedStructures.push({
structureId: structure.id,
structureName: structure.structureName,
updatedAt: structure.updatedAt,
});
} }
}) })
); );
...@@ -778,6 +782,8 @@ export class StructuresService { ...@@ -778,6 +782,8 @@ export class StructuresService {
name: user.name, name: user.name,
email: user.email, email: user.email,
phone: user.phone, phone: user.phone,
job: user.job,
employer: user.employer,
structures: await Promise.all( structures: await Promise.all(
user.structuresLink.map(async (id) => { user.structuresLink.map(async (id) => {
return this.findOne(id.toHexString()); return this.findOne(id.toHexString());
......
import { HttpModule } from '@nestjs/common'; import { HttpModule, HttpStatus } from '@nestjs/common';
import { getModelToken } from '@nestjs/mongoose'; import { getModelToken } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { UsersServiceMock } from '../../../test/mock/services/user.mock.service';
import { ConfigurationModule } from '../../configuration/configuration.module'; import { ConfigurationModule } from '../../configuration/configuration.module';
import { CreateEmployerDto } from '../dto/create-employer.dto';
import { Employer } from '../schemas/employer.schema'; import { Employer } from '../schemas/employer.schema';
import { EmployerService } from '../services/employer.service'; import { EmployerService } from '../services/employer.service';
import { UsersService } from '../services/users.service';
import { EmployerController } from './employer.controller'; import { EmployerController } from './employer.controller';
describe('EmployerController', () => { describe('EmployerController', () => {
let controller: EmployerController; let controller: EmployerController;
let userService: UsersService;
const employerServiceMock = { const employerServiceMock = {
findAll: jest.fn(), findAll: jest.fn(),
...@@ -18,12 +20,24 @@ describe('EmployerController', () => { ...@@ -18,12 +20,24 @@ describe('EmployerController', () => {
create: jest.fn(), create: jest.fn(),
deleteByName: jest.fn(), deleteByName: jest.fn(),
initEmployerIndex: jest.fn(), initEmployerIndex: jest.fn(),
findOne: jest.fn(),
deleteOneId: jest.fn(),
findAllValidated: jest.fn(),
findAllUnvalidated: jest.fn(),
validate: jest.fn(),
update: jest.fn(),
mergeEmployer: jest.fn(),
deleteInvalidEmployer: jest.fn(),
}; };
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
imports: [ConfigurationModule, HttpModule], imports: [ConfigurationModule, HttpModule],
providers: [ providers: [
{
provide: UsersService,
useClass: UsersServiceMock,
},
{ {
provide: EmployerService, provide: EmployerService,
useValue: employerServiceMock, useValue: employerServiceMock,
...@@ -37,6 +51,7 @@ describe('EmployerController', () => { ...@@ -37,6 +51,7 @@ describe('EmployerController', () => {
}).compile(); }).compile();
controller = module.get<EmployerController>(EmployerController); controller = module.get<EmployerController>(EmployerController);
userService = module.get<UsersService>(UsersService);
}); });
it('should be defined', () => { it('should be defined', () => {
...@@ -77,89 +92,208 @@ describe('EmployerController', () => { ...@@ -77,89 +92,208 @@ describe('EmployerController', () => {
expect(findAllReply.length).toBe(2); expect(findAllReply.length).toBe(2);
}); });
}); });
describe('createEmployer', () => {
it('should create new employer `Metro`', async () => { // describe('deleteEmployer', () => {
const newEmployer: CreateEmployerDto = { // it('should delete employer `Metro`', async () => {
name: 'Metro', // const employer: Employer = {
}; // name: 'Metro',
const newCreatedEmployer: Employer = { // validated: true,
name: 'Metro', // };
validated: false, // const employerToRemove: CreateEmployerDto = {
}; // name: 'Metro',
// };
// employerServiceMock.findByName.mockResolvedValueOnce(employer);
// employerServiceMock.deleteByName.mockResolvedValueOnce({
// ok: 1,
// n: 1,
// deletedCount: 1,
// });
// const deleteEmployer = await controller.deleteEmployer(employerToRemove);
// expect(deleteEmployer.name).toBe('Metro');
// expect(deleteEmployer.validated).toBe(true);
// });
// it('should throw error on unexisting employer `Metro`', async () => {
// const employerToRemove: CreateEmployerDto = {
// name: 'Metro',
// };
// employerServiceMock.deleteByName.mockResolvedValueOnce(null);
// try {
// await controller.deleteEmployer(employerToRemove);
// expect(true).toBe(false);
// } catch (e) {
// expect(e.message).toBe('Employer does not exist');
// expect(e.status).toBe(404);
// }
// });
// });
describe('resetES', () => {
it('should reset search index', async () => {
employerServiceMock.initEmployerIndex.mockResolvedValueOnce([
{
_id: Types.ObjectId('6231aefe76598527c8d0b5a7'),
name: 'CAF',
validated: true,
},
{
_id: Types.ObjectId('6231aefe76598527c8d0b5a7'),
name: 'CARSAT',
validated: true,
},
]);
const index = await controller.resetES();
expect(index.length).toBe(2);
});
});
describe('Create Employer', () => {
it('should create a employer', async () => {
employerServiceMock.findByName.mockResolvedValueOnce(null); employerServiceMock.findByName.mockResolvedValueOnce(null);
employerServiceMock.create.mockResolvedValueOnce(newCreatedEmployer); employerServiceMock.create.mockResolvedValueOnce({
const createReply = await controller.createEmployer(newEmployer); _id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
expect(createReply).toEqual(newCreatedEmployer); name: 'Sopra',
validated: true,
});
const req = { user: { _id: '6036721022462b001334c4bb', role: 0 } };
const reply = await controller.createEmployer({ name: 'Sopra' }, req);
expect(reply).toBeTruthy();
}); });
it('should throw error on already existing employer `Metro`', async () => { it('should not create if employer already exists', async () => {
const newEmployer: CreateEmployerDto = {
name: 'Metro',
};
employerServiceMock.findByName.mockResolvedValueOnce({ employerServiceMock.findByName.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5a7'), _id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Metro', name: 'Sopra',
validated: true, validated: true,
}); });
const req = { user: { _id: '6036721022462b001334c4bb' }, role: 0 };
try { try {
await controller.createEmployer(newEmployer); await controller.createEmployer({ name: 'Sopra' }, req);
expect(true).toBe(false); expect;
} catch (e) { } catch (e) {
expect(e.message).toBe('Employer already exist'); expect(e.message).toBe('Employer already exist');
expect(e.status).toBe(422); expect(e.status).toBe(HttpStatus.UNPROCESSABLE_ENTITY);
} }
}); });
it('should call create with send notification to true if admin', async () => {
employerServiceMock.findByName.mockResolvedValueOnce(null);
const employer = {
name: 'Sopra',
};
employerServiceMock.create.mockResolvedValueOnce({ ...employer, validated: true });
const req = { user: { _id: '6036721022462b001334c4bb', role: 1 } };
const reply = await controller.createEmployer(employer, req);
expect(reply).toBeTruthy();
expect(employerServiceMock.create).toHaveBeenCalledWith(employer, true, false);
});
}); });
describe('Validate Employer', () => {
it('should validate an employer', async () => {
employerServiceMock.validate.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Sopra',
validated: true,
});
expect(await controller.validateEmployer({ id: '6231aefe76598527c8d0b5bc' })).toBeTruthy();
});
});
describe('Get Employers', () => {
it('should call all validated employers and populate them with users attached to it', async () => {
const spyer = jest.spyOn(userService, 'populateEmployerswithUsers');
employerServiceMock.findAllValidated.mockResolvedValueOnce([
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Metro',
validated: true,
},
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Sopra',
validated: true,
},
]);
await controller.findValidatedEmployers();
expect(employerServiceMock.findAllValidated.mock.calls.length).toBe(1);
expect(spyer.mock.calls.length).toBe(1);
});
describe('deleteEmployer', () => { it('should call all unvalidated employers and populate them with users attached to it', async () => {
it('should delete employer `Metro`', async () => { const spyer = jest.spyOn(userService, 'populateEmployerswithUsers');
const employer: Employer = { employerServiceMock.findAllUnvalidated.mockResolvedValueOnce([
name: 'Metro', {
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Metro',
validated: false,
},
{
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Sopra',
validated: false,
},
]);
await controller.findUnvalidatedEmployers();
expect(employerServiceMock.findAllUnvalidated.mock.calls.length).toBe(1);
expect(spyer.mock.calls.length).toBe(1);
});
});
describe('Edit Employer', () => {
it('should update employer', async () => {
employerServiceMock.update.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'SopraMod',
validated: true, validated: true,
}; });
const employerToRemove: CreateEmployerDto = { expect(await controller.updateEmployer('6231aefe76598527c8d0b5bc', { name: 'SopraMod' })).toBeTruthy();
name: 'Metro', });
};
employerServiceMock.findByName.mockResolvedValueOnce(employer); it('should delete an unvalidated employer and replace all its occurence with a chosen validated employer', async () => {
employerServiceMock.deleteByName.mockResolvedValueOnce({ employerServiceMock.mergeEmployer.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Sopra',
validated: true,
});
employerServiceMock.deleteInvalidEmployer.mockResolvedValueOnce({
n: 1,
ok: 1, ok: 1,
deletedCount: 1,
});
const reply = await controller.mergeEmployer({
sourceEmployerId: '6231aefe76598527c8d0b5ba',
targetEmployerId: '6231aefe76598527c8d0b5bc',
});
expect(reply).toBeTruthy();
});
});
describe('Delete Employer', () => {
it('should delete employer', async () => {
employerServiceMock.deleteOneId.mockResolvedValueOnce({
n: 1, n: 1,
ok: 1,
deletedCount: 1, deletedCount: 1,
}); });
const deleteEmployer = await controller.deleteEmployer(employerToRemove);
expect(deleteEmployer.name).toBe('Metro'); employerServiceMock.findOne.mockResolvedValueOnce({
expect(deleteEmployer.validated).toBe(true); _id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Sopra',
validated: true,
});
const reply = await controller.deleteEmployer({ id: '6231aefe76598527c8d0b5ba' });
expect(reply).toBeTruthy();
}); });
it('should throw error on unexisting employer `Metro`', async () => { it('should not delete employer if a user is linked', async () => {
const employerToRemove: CreateEmployerDto = { employerServiceMock.findOne.mockResolvedValueOnce({
name: 'Metro', _id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
}; name: 'Sopra',
employerServiceMock.deleteByName.mockResolvedValueOnce(null); validated: true,
});
try { try {
await controller.deleteEmployer(employerToRemove); await controller.deleteEmployer({ id: '6231aefe76598527c8d0b5bc' });
expect(true).toBe(false); expect(true).toBe(false);
} catch (e) { } catch (e) {
expect(e.message).toBe('Employer does not exist'); expect(e.message).toEqual('Cannot delete employer. It has user(s) attached to it.');
expect(e.status).toBe(404); expect(e.status).toEqual(HttpStatus.FORBIDDEN);
} }
}); });
}); });
describe('resetES', () => {
it('should reset search index', async () => {
employerServiceMock.initEmployerIndex.mockResolvedValueOnce([
{
_id: Types.ObjectId('6231aefe76598527c8d0b5a7'),
name: 'CAF',
validated: true,
},
{
_id: Types.ObjectId('6231aefe76598527c8d0b5a7'),
name: 'CARSAT',
validated: true,
},
]);
const index = await controller.resetES();
expect(index.length).toBe(2);
});
});
}); });
...@@ -6,23 +6,30 @@ import { ...@@ -6,23 +6,30 @@ import {
HttpException, HttpException,
HttpStatus, HttpStatus,
Logger, Logger,
Param,
Post, Post,
Put,
Query, Query,
Request,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { ApiParam } from '@nestjs/swagger'; import { ApiBearerAuth, ApiOperation, ApiParam } from '@nestjs/swagger';
import { MergeEmployerDto } from '../../admin/dto/merge-employer.dto';
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
import { hasAdminRole } from '../../shared/utils';
import { Roles } from '../decorators/roles.decorator'; import { Roles } from '../decorators/roles.decorator';
import { CreateEmployerDto } from '../dto/create-employer.dto'; import { CreateEmployerDto } from '../dto/create-employer.dto';
import { RolesGuard } from '../guards/roles.guard'; import { RolesGuard } from '../guards/roles.guard';
import { IEmployer } from '../interfaces/employer.interface';
import { Employer } from '../schemas/employer.schema'; import { Employer } from '../schemas/employer.schema';
import { EmployerService } from '../services/employer.service'; import { EmployerService } from '../services/employer.service';
import { UsersService } from '../services/users.service';
@Controller('employer') @Controller('employer')
export class EmployerController { export class EmployerController {
private readonly logger = new Logger(EmployerController.name); private readonly logger = new Logger(EmployerController.name);
constructor(private employerService: EmployerService) {} constructor(private employerService: EmployerService, private usersService: UsersService) {}
/** /**
* Find all employer. If search is given as param, filter on it. Otherwise return everything * Find all employer. If search is given as param, filter on it. Otherwise return everything
...@@ -45,35 +52,21 @@ export class EmployerController { ...@@ -45,35 +52,21 @@ export class EmployerController {
* @returns {Employer} * @returns {Employer}
*/ */
@Post() @Post()
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT')
@ApiParam({ name: 'newEmployer', type: CreateEmployerDto, required: true }) @ApiParam({ name: 'newEmployer', type: CreateEmployerDto, required: true })
public async createEmployer(@Body() newEmployer: CreateEmployerDto): Promise<Employer> { public async createEmployer(@Body() newEmployer: CreateEmployerDto, @Request() req): Promise<Employer> {
this.logger.debug(`createEmployer: ${newEmployer.name}`); this.logger.debug(`createEmployer: ${newEmployer.name}`);
const existingEmployer = await this.employerService.findByName(newEmployer.name); const existingEmployer = await this.employerService.findByName(newEmployer.name);
if (existingEmployer) { if (existingEmployer) {
this.logger.warn(`Employer already exist: ${newEmployer.name}`); this.logger.warn(`Employer already exist: ${newEmployer.name}`);
throw new HttpException('Employer already exist', HttpStatus.UNPROCESSABLE_ENTITY); throw new HttpException('Employer already exist', HttpStatus.UNPROCESSABLE_ENTITY);
} }
return this.employerService.create(newEmployer); // if user is admin, do not send notification
} if (hasAdminRole(req.user)) {
return this.employerService.create(newEmployer, true, false);
/**
* Delete Employer if exist
* @param employer {CreateEmployerDto} - Employer to delete
* @returns {Employer}
*/
@Delete()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@ApiParam({ name: 'employer', type: CreateEmployerDto, required: true })
public async deleteEmployer(@Body() employer: CreateEmployerDto): Promise<Employer> {
this.logger.debug(`deleteEmployer: ${employer.name}`);
const existingEmployer = await this.employerService.findByName(employer.name);
if (!existingEmployer) {
this.logger.warn(`Employer does not exist: ${employer.name}`);
throw new HttpException('Employer does not exist', HttpStatus.NOT_FOUND);
} }
await this.employerService.deleteByName(employer.name); return this.employerService.create(newEmployer);
return existingEmployer;
} }
// SEARCH // SEARCH
...@@ -88,4 +81,82 @@ export class EmployerController { ...@@ -88,4 +81,82 @@ export class EmployerController {
public async resetES(): Promise<Employer[]> { public async resetES(): Promise<Employer[]> {
return this.employerService.initEmployerIndex(); return this.employerService.initEmployerIndex();
} }
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT')
@Roles('admin')
@Get('validated')
@ApiOperation({ description: 'Get validated employers populated with users attached to it' })
public async findValidatedEmployers() {
return this.employerService.findAllValidated().then(async (employers: IEmployer[]) => {
return this.usersService.populateEmployerswithUsers(employers);
});
}
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT')
@Roles('admin')
@Get('unvalidated')
@ApiOperation({ description: 'Get unvalidated employers for validation or merge' })
public async findUnvalidatedEmployers() {
return this.employerService.findAllUnvalidated().then(async (employers: IEmployer[]) => {
return this.usersService.populateEmployerswithUsers(employers);
});
}
/*
** All users attached to this employer will be affected to the targeted employer
** The original unvalidated employer will be deleted
*/
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT')
@Roles('admin')
@Put('merge')
public async mergeEmployer(@Body() mergeEmployer: MergeEmployerDto) {
return this.employerService.mergeEmployer(mergeEmployer);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT')
@Roles('admin')
@Post('validate/:id')
@ApiParam({ name: 'id', type: String, required: true })
@ApiOperation({ description: 'Validate employer' })
public async validateEmployer(@Param() params) {
return this.employerService.validate(params.id);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT')
@Roles('admin')
@Put(':id')
public async updateEmployer(@Param('id') id: string, @Body() body: CreateEmployerDto) {
return this.employerService.update(id, body);
}
/**
* Delete Employer if exist
* @param employer {CreateEmployerDto} - Employer to delete
* @returns {Employer}
*/
@Delete(':id')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@ApiParam({ name: 'employer', type: CreateEmployerDto, required: true })
public async deleteEmployer(@Param() params): Promise<Employer> {
this.logger.debug(`deleteEmployer: ${params.id}`);
// look for employer
const researchedEmployer = await this.employerService.findOne(params.id);
// look for any relations within user collection, reject action if found
if (researchedEmployer !== null) {
const isEmployerLinked = await this.usersService.isEmployerLinkedtoUser(researchedEmployer._id);
if (!isEmployerLinked) {
return this.employerService.deleteOneId(params.id);
} else {
throw new HttpException('Cannot delete employer. It has user(s) attached to it.', HttpStatus.FORBIDDEN);
}
} else {
throw new HttpException('Employer does not exists', HttpStatus.NOT_FOUND);
}
}
} }