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 @@
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[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 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
### Base de donnée
```bash
$ docker-compose up -d database-ram
$ docker-compose up -d mongo-express
......@@ -70,17 +69,20 @@ $ npm run start:prod
```bash
# Lien vers le swagger
$ http://localhost:3000/api
$ http://localhost:3000/doc
# Lien vers le mongo-express
$ http://localhost:8081
```
## Documentation
## Documentation
A documentation is generated with compodoc in addition of the existing documentation on the wiki.
```sh
npm run doc:serve
```
You can now visualize it at : `localhost:8080`
## Test
......
......@@ -18,7 +18,7 @@
"release": "standard-version",
"init-db": "node ./scripts/init-db.js",
"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: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",
......
import { HttpModule } from '@nestjs/common';
import { getModelToken } from '@nestjs/mongoose';
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 { MailerService } from '../mailer/mailer.service';
import { NewsletterSubscription } from '../newsletter/newsletter-subscription.schema';
import { NewsletterService } from '../newsletter/newsletter.service';
import { SearchModule } from '../search/search.module';
import { Structure } from '../structures/schemas/structure.schema';
import { StructuresService } from '../structures/services/structures.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 { 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 { 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', () => {
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 () => {
const module: TestingModule = await Test.createTestingModule({
......@@ -41,6 +80,18 @@ describe('AdminController', () => {
},
StructuresSearchService,
MailerService,
{
provide: EmployerService,
useValue: mockEmployerService,
},
{
provide: JobsService,
useValue: mockJobService,
},
{
provide: EmployerSearchService,
useValue: mockEmployerSearchService,
},
{
provide: getModelToken('User'),
useValue: User,
......@@ -63,6 +114,7 @@ describe('AdminController', () => {
.compile();
controller = module.get<AdminController>(AdminController);
userService = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
......@@ -71,7 +123,7 @@ describe('AdminController', () => {
it('should get pending attachments', async () => {
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', () => {
......@@ -80,9 +132,10 @@ describe('AdminController', () => {
structureId: '6093ba0e2ab5775cfc01ed3e',
structureName: 'test',
userEmail: 'jean.paul@mii.com',
updatedAt: '2021-03-02T10:07:48.000Z',
};
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 () => {
......@@ -90,6 +143,7 @@ describe('AdminController', () => {
structureId: '1093ba0e2ab5775cfc01z2ki',
structureName: 'test',
userEmail: 'jean.paul@mii.com',
updatedAt: '2021-03-02T10:07:48.000Z',
};
try {
await controller.validatePendingStructure(pendingStructureTest);
......@@ -106,9 +160,10 @@ describe('AdminController', () => {
structureId: '6093ba0e2ab5775cfc01ed3e',
structureName: 'test',
userEmail: 'jean.paul@mii.com',
updatedAt: '2021-03-02T10:07:48.000Z',
};
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 () => {
......@@ -116,6 +171,7 @@ describe('AdminController', () => {
structureId: '1093ba0e2ab5775cfc01z2ki',
structureName: 'test',
userEmail: 'jean.paul@mii.com',
updatedAt: '2021-03-02T10:07:48.000Z',
};
try {
await controller.refusePendingStructure(pendingStructureTest);
......@@ -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', () => {
it('should return all users, empty string', async () => {
expect((await controller.searchUsers({ searchString: '' })).length).toBe(2);
......@@ -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 () => {
expect((await controller.getNewsletterSubscriptions({ searchString: '' })).length).toBe(3);
});
......
import { ApiOperation, ApiParam } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import {
Body,
Delete,
Param,
Controller,
Delete,
Get,
Post,
UseGuards,
HttpStatus,
HttpException,
HttpStatus,
Logger,
Param,
Post,
Put,
UseGuards,
} 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 { NewsletterService } from '../newsletter/newsletter.service';
import { Structure } from '../structures/schemas/structure.schema';
import { StructuresService } from '../structures/services/structures.service';
import { Roles } from '../users/decorators/roles.decorator';
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 { PendingStructureDto } from './dto/pending-structure.dto';
import { validate } from 'class-validator';
import { Structure } from '../structures/schemas/structure.schema';
import { IUser } from '../users/interfaces/user.interface';
import { SetUserEmployerDto } from './dto/set-user-employer.dto';
import { SetUserJobDto } from './dto/set-user-job.dto';
@Controller('admin')
export class AdminController {
......@@ -29,18 +35,22 @@ export class AdminController {
constructor(
private usersService: UsersService,
private structuresService: StructuresService,
private jobsService: JobsService,
private employerService: EmployerService,
private newsletterService: NewsletterService
) {}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('pendingStructures')
@ApiOperation({ description: 'Get pending structre for validation' })
@ApiOperation({ description: 'Get pending structures for validation' })
public async getPendingAttachments(): Promise<PendingStructureDto[]> {
const pendingStructure = await this.usersService.getPendingStructures();
return Promise.all(
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;
})
);
......@@ -49,13 +59,30 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('adminStructuresList')
@ApiOperation({ description: 'Get pending structre for validation' })
@ApiOperation({ description: 'Get pending structures for validation' })
public async getAdminStructuresList(): Promise<any> {
const structuresList = { claimed: [], inClaim: [], toClaim: [], incomplete: [] };
structuresList.inClaim = await this.getPendingAttachments();
structuresList.toClaim = (await this.structuresService.findAllUnclaimed()).filter(
(demand) => !structuresList.inClaim.find((elem) => elem.structureId == demand.structureId)
);
const today = DateTime.local().setZone('utc', { keepLocalTime: true });
const inClaimStructures = await this.getPendingAttachments();
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();
structuresList.claimed = allStructures
.filter(
......@@ -64,14 +91,25 @@ export class AdminController {
!structuresList.toClaim.find((elem) => elem.structureId == demand.id)
)
.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(
allStructures.map(async (struct) => {
const validity = await validate(new Structure(struct));
if (validity.length > 0) {
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 {
this.logger.debug('getAdminStructuresList - validation succeed');
return null;
......@@ -141,6 +179,7 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Delete('user/:id')
@ApiBearerAuth('JWT')
@ApiParam({ name: 'id', type: String, required: true })
public async deleteUser(@Param() params) {
const user = await this.usersService.deleteOneId(params.id);
......@@ -156,6 +195,7 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@ApiBearerAuth('JWT')
@Post('searchUsers')
public async searchUsers(@Body() searchString: { searchString: string }) {
if (searchString && searchString.searchString && searchString.searchString.length > 0)
......@@ -165,14 +205,28 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('getUnAttachedUsers')
@ApiBearerAuth('JWT')
@Get('unAttachedUsers')
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)
@Roles('admin')
@Get('getAttachedUsers')
@ApiBearerAuth('JWT')
@Get('attachedUsers')
public async findAttachedUsers() {
return this.usersService.findAllAttached().then(async (users: IUser[]) => {
return this.structuresService.getAllUserCompletedStructures(users);
......@@ -180,8 +234,9 @@ export class AdminController {
}
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth('JWT')
@Roles('admin')
@Get('getUnVerifiedUsers')
@Get('unVerifiedUsers')
public async findUnVerifiedUsers() {
return this.usersService.findAllUnVerified().then(async (users: IUser[]) => {
return this.structuresService.getAllUserCompletedStructures(users);
......@@ -190,6 +245,7 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@ApiBearerAuth('JWT')
@Post('searchNewsletterSubscriptions')
public async getNewsletterSubscriptions(@Body() searchString: { searchString: string }) {
if (searchString && searchString.searchString && searchString.searchString.length > 0)
......@@ -199,6 +255,7 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@ApiBearerAuth('JWT')
@Get('countNewsletterSubscriptions')
public async countNewsletterSubscriptions(): Promise<number> {
return this.newsletterService.countNewsletterSubscriptions();
......@@ -206,9 +263,60 @@ export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@ApiBearerAuth('JWT')
@Delete('newsletterSubscription/:email')
@ApiParam({ name: 'email', type: String, required: true })
public async unsubscribeUserFromNewsletter(@Param() params): Promise<NewsletterSubscription> {
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 { IsEmail, IsMongoId, IsNotEmpty, IsString } from 'class-validator';
import { IsDate, IsEmail, IsMongoId, IsNotEmpty, IsString } from 'class-validator';
export class PendingStructureDto {
@IsNotEmpty()
......@@ -16,4 +16,9 @@ export class PendingStructureDto {
@IsString()
@ApiProperty({ type: 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 { IsNotEmpty, IsString } from 'class-validator';
import { IsDate, IsNotEmpty, IsString } from 'class-validator';
export class UnclaimedStructureDto {
@IsNotEmpty()
......@@ -11,4 +11,9 @@ export class UnclaimedStructureDto {
@IsString()
@ApiProperty({ type: String })
structureName: string;
@IsNotEmpty()
@IsDate()
@ApiProperty({ type: String })
updatedAt: string;
}
......@@ -61,6 +61,14 @@ export const config = {
ejs: 'structureDeletionNotification.ejs',
json: 'structureDeletionNotification.json',
},
adminJobCreate: {
ejs: 'adminJobCreate.ejs',
json: 'adminJobCreate.json',
},
adminEmployerCreate: {
ejs: 'adminEmployerCreate.ejs',
json: 'adminEmployerCreate.json',
},
contactMessage: {
ejs: 'contactMessage.ejs',
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 { Page } from '../pages/schemas/page.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 {
// Handle image display. Rewrite image URL to fit ghost infra issue.
......@@ -15,3 +17,7 @@ export function rewriteGhostImgUrl(configService: ConfigurationService, itemData
}
return itemData;
}
export function hasAdminRole(user: User): boolean {
return user.role === UserRole.admin;
}
......@@ -240,7 +240,11 @@ export class StructuresService {
await Promise.all(
structures.map(async (structure: StructureDocument) => {
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 {
name: user.name,
email: user.email,
phone: user.phone,
job: user.job,
employer: user.employer,
structures: await Promise.all(
user.structuresLink.map(async (id) => {
return this.findOne(id.toHexString());
......
import { HttpModule } from '@nestjs/common';
import { HttpModule, HttpStatus } from '@nestjs/common';
import { getModelToken } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing';
import { Types } from 'mongoose';
import { UsersServiceMock } from '../../../test/mock/services/user.mock.service';
import { ConfigurationModule } from '../../configuration/configuration.module';
import { CreateEmployerDto } from '../dto/create-employer.dto';
import { Employer } from '../schemas/employer.schema';
import { EmployerService } from '../services/employer.service';
import { UsersService } from '../services/users.service';
import { EmployerController } from './employer.controller';
describe('EmployerController', () => {
let controller: EmployerController;
let userService: UsersService;
const employerServiceMock = {
findAll: jest.fn(),
......@@ -18,12 +20,24 @@ describe('EmployerController', () => {
create: jest.fn(),
deleteByName: 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 () => {
const module: TestingModule = await Test.createTestingModule({
imports: [ConfigurationModule, HttpModule],
providers: [
{
provide: UsersService,
useClass: UsersServiceMock,
},
{
provide: EmployerService,
useValue: employerServiceMock,
......@@ -37,6 +51,7 @@ describe('EmployerController', () => {
}).compile();
controller = module.get<EmployerController>(EmployerController);
userService = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
......@@ -77,89 +92,208 @@ describe('EmployerController', () => {
expect(findAllReply.length).toBe(2);
});
});
describe('createEmployer', () => {
it('should create new employer `Metro`', async () => {
const newEmployer: CreateEmployerDto = {
name: 'Metro',
};
const newCreatedEmployer: Employer = {
name: 'Metro',
validated: false,
};
// describe('deleteEmployer', () => {
// it('should delete employer `Metro`', async () => {
// const employer: Employer = {
// name: 'Metro',
// validated: true,
// };
// 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.create.mockResolvedValueOnce(newCreatedEmployer);
const createReply = await controller.createEmployer(newEmployer);
expect(createReply).toEqual(newCreatedEmployer);
employerServiceMock.create.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
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 () => {
const newEmployer: CreateEmployerDto = {
name: 'Metro',
};
it('should not create if employer already exists', async () => {
employerServiceMock.findByName.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5a7'),
name: 'Metro',
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Sopra',
validated: true,
});
const req = { user: { _id: '6036721022462b001334c4bb' }, role: 0 };
try {
await controller.createEmployer(newEmployer);
expect(true).toBe(false);
await controller.createEmployer({ name: 'Sopra' }, req);
expect;
} catch (e) {
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 delete employer `Metro`', async () => {
const employer: Employer = {
name: 'Metro',
it('should call all unvalidated employers and populate them with users attached to it', async () => {
const spyer = jest.spyOn(userService, 'populateEmployerswithUsers');
employerServiceMock.findAllUnvalidated.mockResolvedValueOnce([
{
_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,
};
const employerToRemove: CreateEmployerDto = {
name: 'Metro',
};
employerServiceMock.findByName.mockResolvedValueOnce(employer);
employerServiceMock.deleteByName.mockResolvedValueOnce({
});
expect(await controller.updateEmployer('6231aefe76598527c8d0b5bc', { name: 'SopraMod' })).toBeTruthy();
});
it('should delete an unvalidated employer and replace all its occurence with a chosen validated employer', async () => {
employerServiceMock.mergeEmployer.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5ba'),
name: 'Sopra',
validated: true,
});
employerServiceMock.deleteInvalidEmployer.mockResolvedValueOnce({
n: 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,
ok: 1,
deletedCount: 1,
});
const deleteEmployer = await controller.deleteEmployer(employerToRemove);
expect(deleteEmployer.name).toBe('Metro');
expect(deleteEmployer.validated).toBe(true);
employerServiceMock.findOne.mockResolvedValueOnce({
_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 () => {
const employerToRemove: CreateEmployerDto = {
name: 'Metro',
};
employerServiceMock.deleteByName.mockResolvedValueOnce(null);
it('should not delete employer if a user is linked', async () => {
employerServiceMock.findOne.mockResolvedValueOnce({
_id: Types.ObjectId('6231aefe76598527c8d0b5bc'),
name: 'Sopra',
validated: true,
});
try {
await controller.deleteEmployer(employerToRemove);
await controller.deleteEmployer({ id: '6231aefe76598527c8d0b5bc' });
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Employer does not exist');
expect(e.status).toBe(404);
expect(e.message).toEqual('Cannot delete employer. It has user(s) attached to it.');
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 {
HttpException,
HttpStatus,
Logger,
Param,
Post,
Put,
Query,
Request,
UseGuards,
} 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 { hasAdminRole } from '../../shared/utils';
import { Roles } from '../decorators/roles.decorator';
import { CreateEmployerDto } from '../dto/create-employer.dto';
import { RolesGuard } from '../guards/roles.guard';
import { IEmployer } from '../interfaces/employer.interface';
import { Employer } from '../schemas/employer.schema';
import { EmployerService } from '../services/employer.service';
import { UsersService } from '../services/users.service';
@Controller('employer')
export class EmployerController {
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
......@@ -45,35 +52,21 @@ export class EmployerController {
* @returns {Employer}
*/
@Post()
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT')
@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}`);
const existingEmployer = await this.employerService.findByName(newEmployer.name);
if (existingEmployer) {
this.logger.warn(`Employer already exist: ${newEmployer.name}`);
throw new HttpException('Employer already exist', HttpStatus.UNPROCESSABLE_ENTITY);
}
return this.employerService.create(newEmployer);
}
/**
* 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);
// if user is admin, do not send notification
if (hasAdminRole(req.user)) {
return this.employerService.create(newEmployer, true, false);
}
await this.employerService.deleteByName(employer.name);
return existingEmployer;
return this.employerService.create(newEmployer);
}
// SEARCH
......@@ -88,4 +81,82 @@ export class EmployerController {
public async resetES(): Promise<Employer[]> {
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);
}
}
}