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 (17)
Showing
with 123 additions and 66 deletions
......@@ -36,7 +36,7 @@ build_branch:
image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/docker:18.09
stage: build
only:
- V2.0
- dev
- merge_requests
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
......@@ -63,7 +63,6 @@ deploy_dev:
- deploy
only:
- dev
- V2.0
script:
- cd /home/mps/ram
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
......
......@@ -2,6 +2,25 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [2.0.2](https://forge.grandlyon.com/web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server/compare/v2.0.1...v2.0.2) (2022-12-16)
### Features
* **carto:** update and format lyon arrondissement display ([558c8c8](https://forge.grandlyon.com/web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server/commit/558c8c80cba7ccc11ad58b43fedb36cef748a8ad))
* **search:** add structure search by postcode ([c960616](https://forge.grandlyon.com/web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server/commit/c960616af94997c24e12f23a04aa7269d233146e))
* **structures:** admin now validates structure member in any case ([03d607e](https://forge.grandlyon.com/web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server/commit/03d607e16e72d6fdc33eb0d349523705880ac963))
### Bug Fixes
* **carto:** lockdown structure info display (parameters api 401 Unauthorized) ([cd3f1b7](https://forge.grandlyon.com/web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server/commit/cd3f1b749a233c55bec79359da799dfb58c2d7b8))
* **cicd:** restore pipeline ([b71c2d5](https://forge.grandlyon.com/web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server/commit/b71c2d56f6fd110d9f95586cea638f0171908bd8))
* **configuration:** app url path ([73ba911](https://forge.grandlyon.com/web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server/commit/73ba911c128341564622d0ad794d9a19c5ef5e8d))
* **elasticsearch:** increase search limit because of structure number limit ([c0b7408](https://forge.grandlyon.com/web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server/commit/c0b7408410d4aafd135216de757c6c265da8b8b6))
* **email:** removed unnecessary char ([57138e1](https://forge.grandlyon.com/web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server/commit/57138e1d2078e23b91561d105c8f59d5cb9abdfc))
* retours recette v2 ([53bdb05](https://forge.grandlyon.com/web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server/commit/53bdb050bb9a544fd367792123de224a8db50966))
### [2.0.1](https://forge.grandlyon.com/web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server/compare/v2.0.0...v2.0.1) (2022-12-05)
......
{
"name": "ram_server",
"version": "2.0.1",
"version": "2.0.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......
{
"name": "ram_server",
"private": true,
"version": "2.0.1",
"version": "2.0.2",
"description": "Nest TypeScript starter repository",
"license": "MIT",
"scripts": {
......
......@@ -140,6 +140,7 @@ module.exports = {
numero: '18',
street: 'Rue de la Mairie',
commune: 'Feyzin',
postcode: '69320',
},
accountVerified: true,
coord: [4.8563106, 45.6693619],
......@@ -256,6 +257,7 @@ module.exports = {
numero: '27',
street: 'Avenue de la République',
commune: 'Vénissieux',
postcode: '69259',
},
accountVerified: true,
coord: [4.8678548, 45.7075853],
......@@ -302,6 +304,7 @@ module.exports = {
numero: '7',
street: 'Rue Saint Polycarpe',
commune: 'Lyon 1er',
postcode: '69001',
},
hours: {
monday: {
......@@ -462,6 +465,7 @@ module.exports = {
numero: '30 bis',
street: 'Avenue Leclerc',
commune: 'Rillieux-la-Pape',
postcode: '69286',
},
coord: [4.9036773, 45.8142196],
accountVerified: true,
......@@ -585,6 +589,7 @@ module.exports = {
numero: '212',
street: 'Avenue Franklin Roosevelt',
commune: 'Bron',
postcode: '69500',
},
coord: [4.9149121, 45.7311648],
accountVerified: true,
......@@ -687,6 +692,7 @@ module.exports = {
numero: '3',
street: 'Rue Louis Normand',
commune: 'Oullins',
postcode: '69600',
},
coord: [4.8188378, 45.7157896],
accountVerified: true,
......@@ -797,6 +803,7 @@ module.exports = {
numero: '7',
street: 'Rue Robert et Reynier ',
commune: 'Saint-Fons',
postcode: '69199',
},
coord: [4.8608585, 45.7086482],
accountVerified: true,
......@@ -872,6 +879,7 @@ module.exports = {
numero: '172',
street: 'Avenue Général Frère',
commune: 'Lyon 8e',
postcode: '69008',
},
createdAt: '2020-11-13T14:13:00.000Z',
updatedAt: '2022-04-13T14:13:00.000Z',
......@@ -939,6 +947,7 @@ module.exports = {
numero: '2',
street: 'Rue Neuve',
commune: 'Fleurieu-sur-Saône',
postcode: '69250',
},
createdAt: '2022-01-13T14:13:00.000Z',
updatedAt: '2022-02-13T14:13:00.000Z',
......
......@@ -24,6 +24,7 @@ import { JobsService } from '../users/services/jobs.service';
import { UsersService } from '../users/services/users.service';
import { AdminController } from './admin.controller';
import { AdminService } from './admin.service';
import { PendingStructureDto } from './dto/pending-structure.dto';
describe('AdminController', () => {
let controller: AdminController;
......@@ -62,7 +63,13 @@ describe('AdminController', () => {
mergeJob: jest.fn(),
deleteInvalidJob: jest.fn(),
};
const pendingStructureTest: PendingStructureDto = {
structureId: new Types.ObjectId('6093ba0e2ab5775cfc01ed3e'),
structureName: 'test',
userEmail: 'jean.paul@mii.com',
createdAt: new Date('2021-02-02T10:07:48.000Z'),
updatedAt: new Date('2021-03-02T10:07:48.000Z'),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [ConfigurationModule, HttpModule, SearchModule],
......@@ -133,25 +140,11 @@ describe('AdminController', () => {
describe('Pending structures validation', () => {
it('should validate pending structure', async () => {
const pendingStructureTest = {
structureId: '6093ba0e2ab5775cfc01ed3e',
structureName: 'test',
userEmail: 'jean.paul@mii.com',
createdAt: new Date('2021-02-02T10:07:48.000Z'),
updatedAt: new Date('2021-03-02T10:07:48.000Z'),
};
expect((await controller.validatePendingStructure(pendingStructureTest)).length).toBe(2);
expect(Object.keys((await controller.validatePendingStructure(pendingStructureTest))[0]).length).toBe(5);
});
it('should get structure does not exist', async () => {
const pendingStructureTest = {
structureId: '1093ba0e2ab5775cfc01z2ki',
structureName: 'test',
userEmail: 'jean.paul@mii.com',
createdAt: new Date('2021-02-02T10:07:48.000Z'),
updatedAt: new Date('2021-03-02T10:07:48.000Z'),
};
try {
await controller.validatePendingStructure(pendingStructureTest);
} catch (e) {
......@@ -163,25 +156,11 @@ describe('AdminController', () => {
describe('Pending structures cancel', () => {
it('should refuse pending structure', async () => {
const pendingStructureTest = {
structureId: '6093ba0e2ab5775cfc01ed3e',
structureName: 'test',
userEmail: 'jean.paul@mii.com',
createdAt: new Date('2021-02-02T10:07:48.000Z'),
updatedAt: new Date('2021-03-02T10:07:48.000Z'),
};
expect((await controller.refusePendingStructure(pendingStructureTest)).length).toBe(2);
expect(Object.keys((await controller.refusePendingStructure(pendingStructureTest))[0]).length).toBe(5);
});
it('should get structure does not exist', async () => {
const pendingStructureTest = {
structureId: '1093ba0e2ab5775cfc01z2ki',
structureName: 'test',
userEmail: 'jean.paul@mii.com',
createdAt: new Date('2021-02-02T10:07:48.000Z'),
updatedAt: new Date('2021-03-02T10:07:48.000Z'),
};
try {
await controller.refusePendingStructure(pendingStructureTest);
} catch (e) {
......
......@@ -52,7 +52,6 @@ export class AdminController {
pendingStructure.map(async (structure) => {
const structureDocument = await this.structuresService.findOne(structure.structureId);
structure.structureName = structureDocument.structureName;
structure.createdAt = structureDocument.createdAt;
structure.updatedAt = structureDocument.updatedAt;
return structure;
})
......@@ -135,13 +134,13 @@ export class AdminController {
@Post('validatePendingStructure')
@ApiOperation({ description: 'Validate structure ownership' })
public async validatePendingStructure(@Body() pendingStructureDto: PendingStructureDto) {
const structure = await this.structuresService.findOne(pendingStructureDto.structureId);
const structure = await this.structuresService.findOne(pendingStructureDto.structureId.toString());
if (!structure || structure.deletedAt) {
throw new HttpException('Structure does not exist', HttpStatus.NOT_FOUND);
}
await this.usersService.validatePendingStructure(
pendingStructureDto.userEmail,
pendingStructureDto.structureId,
pendingStructureDto.structureId.toString(),
structure.structureName,
true
);
......@@ -161,13 +160,13 @@ export class AdminController {
@Post('rejectPendingStructure')
@ApiOperation({ description: 'Refuse structure ownership' })
public async refusePendingStructure(@Body() pendingStructureDto: PendingStructureDto) {
const structure = await this.structuresService.findOne(pendingStructureDto.structureId);
const structure = await this.structuresService.findOne(pendingStructureDto.structureId.toString());
if (!structure || structure.deletedAt) {
throw new HttpException('Structure does not exist', HttpStatus.NOT_FOUND);
}
await this.usersService.validatePendingStructure(
pendingStructureDto.userEmail,
pendingStructureDto.structureId,
pendingStructureDto.structureId.toString(),
structure.structureName,
false
);
......
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsMongoId, IsNotEmpty, IsString } from 'class-validator';
import { Types } from 'mongoose';
export class PendingStructureDto {
@IsNotEmpty()
......@@ -9,8 +10,8 @@ export class PendingStructureDto {
@IsNotEmpty()
@IsMongoId()
@ApiProperty({ type: String })
readonly structureId: string;
@ApiProperty({ type: Types.ObjectId })
readonly structureId: Types.ObjectId;
@IsNotEmpty()
@IsString()
......@@ -18,7 +19,7 @@ export class PendingStructureDto {
structureName: string;
@ApiProperty({ type: Date })
updatedAt: Date;
updatedAt?: Date;
@ApiProperty({ type: Date })
createdAt: Date;
......
......@@ -29,7 +29,7 @@ export class ConfigurationService {
break;
}
this._config.templates = config.templates; // Add mail templates
this._appUrl = `${config.protocol}://${config.host}${config.port ? ':' + config.port : ''}`;
this._appUrl = `${this.config.protocol}://${this.config.host}${this.config.port ? ':' + this.config.port : ''}`;
dotenv.config();
}
......
Bonjour,<br />
<br />
Un nouvel employeur <%= employerName %> vient d'être créé,
<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/admin>"
<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/admin"
>cliquez ici pour le valider.</a
>
Bonjour,<br />
<br />
Une nouvelle fonction <%= jobName %> vient d'être créée,
<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/admin>"
<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/admin"
>cliquez ici pour la valider.</a
>
Bonjour,<br />
<br />
<%= name %> <%= surname %> indique travailler au sein de votre structure <%= structureName %>.<br />
S'il s'agit d'une erreur, merci de nous le signaler en cliquant sur le lien ci-dessous.
<br />
<br />
<br />
<div style="text-align: center">
<a
href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/exclude?id=<%= id %>&userId=<%= userId %>"
>Signaler une erreur d'appartenance à cette structure</a
>
</div>
Vous recevez ce message car <strong><%= surname %></strong> <strong><%= name %></strong> indique travailler au sein de
votre stucture <strong><%= structureName %></strong> sur RES'in, le réseau des acteurs de l'inclusion numérique de la
Métropole de Lyon. Vous pouvez dès maintenant
<a
href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/join-validation?id=<%= id %>&userId=<%= userId %>&status=true&token=<%= validationToken %>"
>valider la demande</a
>
ou
<a
href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/join-validation?id=<%= id %>&userId=<%= userId %>&status=false&token=<%= validationToken %>"
>refuser la demande</a
>.
{
"subject": "Un acteur a rejoint votre structure, Réseau des Acteurs de la Médiation Numérique de la Métropole de Lyon"
"subject": "Un acteur demande à rejoindre votre structure, Réseau des Acteurs de la Médiation Numérique de la Métropole de Lyon"
}
import { Db } from 'mongodb';
import { getDb } from '../migrations-utils/db';
import { HttpService } from '@nestjs/axios';
import { lastValueFrom } from 'rxjs';
export const up = async () => {
const db: Db = await getDb();
const cursor = db.collection('structures').find({});
let document;
while ((document = await cursor.next())) {
const newDoc = document;
const httpService = new HttpService();
await lastValueFrom(
httpService.get(
encodeURI(
`https://download.data.grandlyon.com/geocoding/photon/api?q=${document.address.commune}&lang=fr&limit=1`
)
)
).then(async (res) => {
newDoc.address.postcode = res.data.features[0].properties.postcode;
await db.collection('structures').updateOne({ _id: document._id }, [{ $set: newDoc }]);
console.log(
`${document.structureName}, ${document.address.commune} : ${res.data.features[0].properties.postcode}`
);
});
}
console.log('Update done : postcodes added to structures');
};
export const down = async () => {
const db: Db = await getDb();
const cursor = db.collection('structures').find({});
let document;
while ((document = await cursor.next())) {
await db.collection('structures').updateOne({ _id: document._id }, [{ $unset: 'address.postcode' }]);
}
console.log(`Downgrade done : postcodes removed from structures`);
};
......@@ -56,3 +56,7 @@ export const es_settings_homemade_french: IndicesIndexSettings = {
analyzer: analyzer,
},
};
export function escapeElasticsearchQuery(query: string): string {
return query.replace(/(\+|\-|\=|&&|\|\||\>|\<|\!|\(|\)|\{|\}|\[|\]|\^|"|~|\?|\:|\\|\/)/g, '\\$&');
}
......@@ -11,6 +11,8 @@ export class Address {
@IsNotEmpty()
commune: string;
postcode: string;
}
export const AddressSchema = SchemaFactory.createForClass(Address);
......@@ -304,6 +304,7 @@ export class ApticStructuresService {
}
address.commune = this.structureService.getFormattedCity(structure.address.city, structure.address.zip);
address.postcode = structure.address.zip;
return address;
}
}
import { IndicesRefreshResponse, UpdateResponse } from '@elastic/elasticsearch/lib/api/types';
import { Injectable, Logger } from '@nestjs/common';
import { ElasticsearchService } from '@nestjs/elasticsearch';
import { es_settings_homemade_french } from '../../shared/utils';
import { escapeElasticsearchQuery, es_settings_homemade_french } from '../../shared/utils';
import { StructureSearchBody } from '../interfaces/structure-search-body.interface';
import { StructureDocument } from '../schemas/structure.schema';
......@@ -97,14 +97,18 @@ export class StructuresSearchService {
searchString = searchString ? searchString + '*' : '*';
const { hits } = await this.elasticsearchService.search<StructureSearchBody>({
index: this.index,
from: 0,
size: 200,
query: {
query_string: {
analyze_wildcard: true,
query: searchString,
fields: fields ? fields : ['structureName^5', 'structureType^5', 'address.commune^10', 'description'],
fuzziness: 'AUTO',
body: {
from: 0,
size: 500,
query: {
query_string: {
analyze_wildcard: true,
query: escapeElasticsearchQuery(searchString),
fields: fields
? fields
: ['structureName^5', 'structureType^5', 'address.commune^10', 'address.postcode^5', 'description'],
fuzziness: 'AUTO',
},
},
},
});
......
......@@ -159,6 +159,7 @@ const structuresSearchServiceMock = {
numero: '30 bis',
street: 'Avenue Leclerc',
commune: 'Rillieux-la-Pape',
postcode: '69250',
},
coord: [4.9036773, 45.8142196],
accountVerified: true,
......@@ -349,6 +350,7 @@ describe('StructuresService', () => {
numero: '30 bis',
street: 'Avenue Leclerc',
commune: 'Rillieux-la-Pape',
postcode: '69250',
},
coord: [4.9036773, 45.8142196],
accountVerified: true,
......