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
Select Git revision

Target

Select target project
  • web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_server
1 result
Select Git revision
Show changes
Showing
with 172 additions and 34 deletions
Bonjour,<br />
<br />
La fiche structure: <strong><%= name %></strong> a été créée après récupération des données aptic. Elle correspond
potientiellement à la structure existante : <strong><%= duplicatedStructureName %></strong>.
La fiche structure: <strong><%= name %></strong> a été créée après récupération des données Aptic.<br />
Elle correspond potentiellement à la structure existante : <strong><%= duplicatedStructureName %></strong>.<br />
<br />
<a href="<%= duplicatedStructureUrl %>">Aller vérifier la labellisation Pass Numérique de la structure existante</a>
Bonjour,<br />
<br />
Une demande de contact a été envoyée par :<br />
<br />
<%= name %><br />
<%= email %><br />
<%if (phone) { %><%= phone %><br /><% } %>
<br />
<strong><%= subject %></strong><br />
<%# Unescaped raw output with tag %- (for html new line tags), cf. https://github.com/mde/ejs %> <%- message %><br />
{
"subject": "Demande de contact"
}
......@@ -90,11 +90,7 @@ export class MailerService {
* @param html
*/
public addSignature(html: string): string {
html += `<br /><br /><p>L’équipe projet inclusion numérique.</p><img src="${this.config.protocol}://${
this.config.host
}${
this.config.port ? ':' + this.config.port : ''
}/assets/logos/resin.jpg" alt="Logo resin" width="168" height="58"><br /><br /><p>Ce mail est automatique. Merci de ne pas y répondre.</p>`;
html += `<br /><br /><p>L’équipe projet inclusion numérique.</p><img src="${this.configurationService.appUrl}/assets/logos/resin.jpg" alt="Logo resin" width="168" height="58"><br /><br /><p>Ce mail est automatique. Merci de ne pas y répondre.</p>`;
return html;
}
}
......@@ -72,12 +72,14 @@ export class PostsService {
if (!postData.custom_excerpt) {
postData.excerpt = 'Inconnu';
}
const test = `<p>Test qui va bien Test qui va bienTest qui va bienTest qui va bienTest qui va bienTest qui va bienTest qui va bienTest qui va bienTest qui va bienTest qui va bienTest qui va bienTest qui va bienTest qui va bienTest qui va bienTest qui va bienTest qui va bienTest qui va bien.</p><figure class=\"kg-card kg-image-card\"><img src=\"https://resin-dev.grandlyon.com/content/images/2022/01/resin-logo-1200x630.png\" class=\"kg-image\" alt loading=\"lazy\" width=\"1200\" height=\"630\" srcset=\"https://resin-dev.grandlyon.com/content/images/size/w600/2022/01/resin-logo-1200x630.png 600w, https://resin-dev.grandlyon.com/content/images/size/w1000/2022/01/resin-logo-1200x630.png 1000w, https://resin-dev.grandlyon.com/content/images/2022/01/resin-logo-1200x630.png 1200w\" sizes=\"(min-width: 720px) 720px\"></figure>`;
// Handle image display. Rewrite image URL to fit ghost infra issue.
if (postData.feature_image && !this.configService.isLocalConf()) {
postData.feature_image = `https://${this.configService.config.host}/blog/content${
postData.feature_image.split('/content')[1]
}`;
if (!this.configService.isLocalConf()) {
if (postData.feature_image) {
postData.feature_image = `https://${this.configService.config.host}/blog/content${
postData.feature_image.split('/content')[1]
}`;
}
const regex = /(https?:\/\/ghost):(\d*)?/g;
postData.html = postData.html.replace(regex, `https://${this.configService.config.host}/blog`);
}
......
export const depRegex = /69[0-9]{3}/g;
......@@ -241,7 +241,7 @@ export class ApticStructuresService {
})
.exec();
if (sameAddrStructure) {
this.userService.sendAdminApticStructureMail(createdStructure.structureName, sameAddrStructure.structureName);
this.userService.sendAdminApticStructureMail(createdStructure.structureName, sameAddrStructure);
}
}
......
......@@ -21,6 +21,7 @@ import { CategoriesAccompagnement } from '../../categories/schemas/categoriesAcc
import { CategoriesFormations } from '../../categories/schemas/categoriesFormations.schema';
import { CategoriesOthers } from '../../categories/schemas/categoriesOthers.schema';
import { UnclaimedStructureDto } from '../../admin/dto/unclaimed-structure-dto';
import { depRegex } from '../common/regex';
@Injectable()
export class StructuresService {
......@@ -340,12 +341,13 @@ export class StructuresService {
/**
* Search structure address based on data search WS
* @param {searchQuery} data - Query address
*/
public async searchAddress(data: { searchQuery: string }): Promise<AxiosResponse<any>> {
const req =
'https://download.data.grandlyon.com/geocoding/photon-bal/api?q=' +
data.searchQuery +
'&lat=45.75&lon=4.85&lang=fr&limit=50&osm_tag=:!construction&osm_tag=:!bus_stop';
public async searchAddress(data: {
searchQuery: string;
}): Promise<AxiosResponse<{ features: { geometry: {}; type: string; properties: {} }[] }>> {
const req = `https://download.data.grandlyon.com/geocoding/photon/api?q=${data.searchQuery}&lang=fr&limit=500&osm_tag=:!construction&osm_tag=:!bus_stop`;
Logger.debug(`Search request: ${encodeURI(req)}`, 'StructureService');
return new Promise((resolve, reject) => {
this.httpService
.request({
......@@ -355,10 +357,16 @@ export class StructuresService {
})
.subscribe(
(reply) => {
Logger.debug(`Search request response length : ${reply.data.features.length}`, 'StructureService');
reply.data.features = reply.data.features
.filter((doc) => doc.properties.postcode && doc.properties.postcode.match(depRegex))
.sort((a, b) => {
return b.properties.housenumber ? 1 : -1;
});
return resolve(reply.data);
},
(err) => {
Logger.error(`Request error: ${err.config.url}`, 'StructureService - search');
Logger.error(`Search - Request error: ${err.config.url}`, 'StructureService');
Logger.error(err);
}
);
......@@ -372,7 +380,7 @@ export class StructuresService {
*/
public async countByStructureKey(key: string, selected: { id: string; text: string }[]): Promise<any> {
const uniqueElements = await this.structureModel.distinct(key).exec();
return await Promise.all(
return Promise.all(
uniqueElements.map(async (value) => {
const keyList: FilterQuery<DocumentDefinition<StructureDocument>>[] = [];
keyList.push({
......@@ -402,9 +410,8 @@ export class StructuresService {
}
public getCoord(numero: string, address: string, zipcode: string): Observable<AxiosResponse<any>> {
const req =
'https://download.data.grandlyon.com/geocoding/photon-bal/api' + '?q=' + numero + ' ' + address + ' ' + zipcode;
Logger.log(`Request : ${req}`, 'StructureService - getCoord');
const req = 'https://download.data.grandlyon.com/geocoding/photon/api?q=' + numero + ' ' + address + ' ' + zipcode;
Logger.log(`getCoord - Request : ${req}`, 'StructureService');
return this.httpService.get(encodeURI(req));
}
......@@ -609,4 +616,23 @@ export class StructuresService {
private hasTempMail(structure: Structure): boolean {
return structure.contactMail === 'unknown@unknown.com';
}
public async getAllUserCompletedStructures(users: IUser[]): Promise<any[]> {
return Promise.all(
users.map(async (user) => {
return {
id: user._id,
surname: user.surname,
name: user.name,
email: user.email,
phone: user.phone,
structures: await Promise.all(
user.structuresLink.map(async (id) => {
return await this.findOne(id.toHexString());
})
),
};
})
);
}
}
......@@ -30,6 +30,7 @@ import { structureDto } from './dto/structure.dto';
import { Structure, StructureDocument } from './schemas/structure.schema';
import { StructuresService } from './services/structures.service';
import { RolesGuard } from '../users/guards/roles.guard';
import { depRegex } from './common/regex';
@Controller('structures')
export class StructuresController {
......@@ -51,11 +52,17 @@ export class StructuresController {
@Get('coordinates/:zipcode')
@ApiParam({ name: 'zipcode', type: String, required: true })
public async getCoordinates(@Param('zipcode') city: string): Promise<any> {
return await this.httpService
.get(encodeURI('https://download.data.grandlyon.com/geocoding/photon-bal/api?q=' + city))
return this.httpService
.get(encodeURI(`https://download.data.grandlyon.com/geocoding/photon/api?q=${city}`))
.toPromise()
.then(async (res) => res.data.features)
.then((data) => data.filter((cityPoint) => cityPoint.properties.city.toLowerCase().includes(city.toLowerCase())))
.then((data) =>
data.filter(
(cityPoint) =>
cityPoint.properties.city?.toLowerCase().includes(city.toLowerCase()) &&
cityPoint.properties.postcode.match(depRegex)
)
)
.then((data) => data.map((filteredCityPoint) => filteredCityPoint.geometry.coordinates));
}
......@@ -66,7 +73,7 @@ export class StructuresController {
@Post('search')
public async search(@Query() query: QueryStructure, @Body() body): Promise<Structure[]> {
return await this.structureService.searchForStructures(query.query, body ? body.filters : null);
return this.structureService.searchForStructures(query.query, body ? body.filters : null);
}
@Post('resetSearchIndex')
......@@ -138,7 +145,7 @@ export class StructuresController {
@Post('address')
public async searchAddress(@Body() data: { searchQuery: string }) {
return await this.structureService.searchAddress(data);
return this.structureService.searchAddress(data);
}
@Get(':id')
......
......@@ -8,6 +8,8 @@ import { HttpException, HttpStatus } from '@nestjs/common';
import { LoginDto } from '../auth/login-dto';
import { EmailChangeDto } from './dto/change-email.dto';
import * as bcrypt from 'bcrypt';
import { ConfigurationModule } from '../configuration/configuration.module';
import { IUser } from './interfaces/user.interface';
function hashPassword() {
return bcrypt.hashSync(process.env.USER_PWD, process.env.SALT);
......@@ -18,7 +20,7 @@ describe('UsersService', () => {
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [MailerModule],
imports: [MailerModule, ConfigurationModule],
providers: [
UsersService,
{
......@@ -288,4 +290,41 @@ describe('UsersService', () => {
expect(await service.updateStructureLinked('test@mii.com', '6001a37716b08100062e4160')).toBe(result);
});
});
it('should find All Unattacheduser', async () => {
const result = [
{
_id: '123',
validationToken:
'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42',
emailVerified: false,
email: 'jacques.dupont@mii.com',
password: hashPassword(),
role: 0,
newEmail: '',
changeEmailToken: '',
resetPasswordToken: null,
structuresLink: [],
pendingStructuresLink: [],
structureOutdatedMailSent: [],
name: 'Jacques',
surname: 'Dupont',
phone: '06 06 06 06 06',
} as IUser,
];
jest.spyOn(service, 'findAllUnattached').mockImplementation(async (): Promise<IUser[]> => result);
expect(await service.findAllUnattached()).toBe(result);
});
it('should find attached users', async () => {
const result = [];
jest.spyOn(service, 'findAllAttached').mockImplementation(async (): Promise<IUser[]> => result);
expect((await service.findAllAttached()).length).toBe(0);
});
it('should find UnVerified Users', async () => {
const result = [];
jest.spyOn(service, 'findAllUnVerified').mockImplementation(async (): Promise<IUser[]> => result);
expect((await service.findAllUnVerified()).length).toBe(0);
});
});
......@@ -13,10 +13,15 @@ import { EmailChangeDto } from './dto/change-email.dto';
import { PendingStructureDto } from '../admin/dto/pending-structure.dto';
import { OwnerDto } from './dto/owner.dto';
import { StructureDocument } from '../structures/schemas/structure.schema';
import { ConfigurationService } from '../configuration/configuration.service';
@Injectable()
export class UsersService {
constructor(@InjectModel(User.name) private userModel: Model<IUser>, private readonly mailerService: MailerService) {}
constructor(
@InjectModel(User.name) private userModel: Model<IUser>,
private readonly mailerService: MailerService,
private configurationService: ConfigurationService
) {}
/**
* Create a user account
......@@ -77,8 +82,32 @@ export class UsersService {
return this.userModel.findOne({ email: mail }).select('-password').exec();
}
public async findAll(): Promise<User[]> {
return await this.userModel.find().exec();
public findAll(): Promise<User[]> {
return this.userModel.find().exec();
}
public findAllUnattached(): Promise<IUser[]> {
return this.userModel
.find()
.where('emailVerified')
.equals(true)
.where('structuresLink')
.size(0)
.sort({ surname: 1 })
.exec();
}
public findAllAttached(): Promise<IUser[]> {
return this.userModel
.find({ $where: 'this.structuresLink.length>0' })
.where('emailVerified')
.equals(true)
.sort({ surname: 1 })
.exec();
}
public findAllUnVerified(): Promise<IUser[]> {
return this.userModel.find().where('emailVerified').equals(false).sort({ surname: 1 }).exec();
}
public async findById(id: string, passwordQuery?: boolean): Promise<IUser | undefined> {
......@@ -142,7 +171,10 @@ export class UsersService {
/**
* Send to all admins mail for aptic duplicated data
*/
public async sendAdminApticStructureMail(structureName: string, duplicatedStructureName: string): Promise<any> {
public async sendAdminApticStructureMail(
structureName: string,
duplicatedStructure: StructureDocument
): Promise<any> {
const config = this.mailerService.config;
const ejsPath = this.mailerService.getTemplateLocation(config.templates.apticStructureDuplication.ejs);
const jsonConfig = this.mailerService.loadJsonConfig(config.templates.apticStructureDuplication.json);
......@@ -150,7 +182,8 @@ export class UsersService {
const html = await ejs.renderFile(ejsPath, {
config,
name: structureName,
duplicatedStructureName: duplicatedStructureName,
duplicatedStructureName: duplicatedStructure.structureName,
duplicatedStructureUrl: this.configurationService.appUrl + '/acteurs?id=' + duplicatedStructure._id,
});
const admins = await this.getAdmins();
admins.forEach((admin) => {
......
......@@ -13,6 +13,7 @@ ME_PORT=<mongo express port>
SALT=<Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue>
MAIL_URL=<SEN API url>
MAIL_TOKEN=<SEN API token>
MAIL_CONTACT=<MAIL_CONTACT>
APTIC_TOKEN=<APTIC API TOKEN>
GHOST_PORT=<ghost port>
GHOST_DB_PASSWORD=<ghost db password>
......
......@@ -865,4 +865,8 @@ export class StructuresServiceMock {
__v: 0,
};
}
async getAllUserCompletedStructures(users: any[]): Promise<any[]> {
return [];
}
}
import { HttpException, HttpStatus } from '@nestjs/common';
import { PendingStructureDto } from '../../../src/admin/dto/pending-structure.dto';
import { LoginDto } from '../../../src/auth/login-dto';
import { User } from '../../../src/users/schemas/user.schema';
export class UsersServiceMock {
findOne(mail: string, passwordQuery?: boolean) {
......@@ -187,4 +188,16 @@ export class UsersServiceMock {
},
];
}
async findAllAttached(): Promise<User[]> {
return await [];
}
async findAllUnattached(): Promise<User[]> {
return await [];
}
async findAllUnVerified(): Promise<User[]> {
return await [];
}
}
......@@ -6,6 +6,7 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
......