Merge branch 'dev' into feature/anchoring-proof

This commit is contained in:
Loïs Mansot 2023-09-22 18:01:59 +02:00
commit 7139f9d737
No known key found for this signature in database
GPG Key ID: 8CF1F4150DDA726D
20 changed files with 177 additions and 56 deletions

View File

@ -20,7 +20,7 @@ jobs:
- setup_remote_docker:
version: 20.10.12
docker_layer_caching: true
- run: docker login rg.fr-par.scw.cloud/lecoffre -u nologin -p $SCW_SECRET_KEY
- run: docker login rg.fr-par.scw.cloud/lecoffre -u nologin -p $SCW_SECRET_KEY_BIS
- run: docker build --tag rg.fr-par.scw.cloud/lecoffre/back:${CIRCLE_SHA1:0:7} .
- run: docker push rg.fr-par.scw.cloud/lecoffre/back:${CIRCLE_SHA1:0:7}
@ -33,7 +33,7 @@ jobs:
parameters:
env:
type: string
default: stg
default: ppd
steps:
- checkout
- kubernetes/install-kubeconfig:

View File

@ -1,6 +1,6 @@
dockerPullSecret: docker-pull-secret
scwSecretKey: AgCgjF5QEzxT3GYTS5B6cmQ0e+0/qFWzKaUDSi+Vjc7RoameuvaIJvTXMBkS3he1oy1ulbB34v6vpZI2kxnGNqERA/U5BaYDAyfKSBwMAy4br7HVKhhuwkoF5qoG5JzJXseSmqB1U9vncVIGOZWzJc1Y4/eGlWcvLcLyfw2z/WEpyeNiWJfEhTYpJOB7gv0XnRb2U/JM3jRy1QgEUIk1WR6kgBalF+xaczPQ6uKh+PR2pqkbZa3WaKUrddmzNsgEz4d8PZMWt8IBwR2JOQEHUqCd34p/pJNyLdUgcdDhg02DKwn1oRoAxKTbAio/a7WrMbodjCb3TNWIYGal5mFmItZ7Ok/EBmUf4E85eOkTR+j8ynuuiexld3Q5Kw3o8LsHjgzVL9uP+T2rYaKkjtVt+YQRX1U8l9CrsdUEz0/wEBA0jwCWMfnh1qhD5pM/xwwjsEEAcK4rYV+Q7iAgGZZvZBCQ5aEHzrtn5D95tr1GZCV2hmrW6Seu+LKKLVBS1JmsuEsOuhudYsEK9m2RYVcxbjuS5eokKEjNrGobf2oB8rhBByavfw1JTBixR5JrI8lcYlnCa+oEhxXKJY+4Fx5SAB4YaLCMSo5vw6zsFQ3WKQzlEmCFt+EnapS+a+MGrdlwq07OHTDpvgk/1z39hopoCuhhKckGGfErLXsTYQvDOkFu+EPzgY7m7qDw/d9pSiht5tuSOkAqeOgm7tpNkUufZhaXmP+1aT7i+H5gq1JILGAmXzTI5Wc=
scwSecretKey: AgChoEnPitXp4Ny/rVMEcevaWKNVpyj2cJYAcq+yFqKwVwnLB+ffDvwqz9XBHu+6d4Nyyjkf37zUAMoaM21lEDWA7x3zfG2/D/j+rvX1qxzZgLD0mjBk7fGElVm332I6JA83oInes8AMMYEDPLElzHnpKRb9KtkIP4NzgOcCeW0ijft3N7Vroez6LEHsBPCA1I9XjKSkGEDvrO0MhWX3iJOlfz+SPMfJAV7rPawOs0ZmohTHrPW8qIvGDn8HCzKyU8zRBoMt+Ogpf5pH4U3JryEFuqD61KAQgablAM8edPIvsgNno9HAEuC2QtRLYA9aUhuKdaKuS58c9P2E80PHWXIlbpFCg6EugQTgNfnYp+3qDUNz8edeCfapYLvF4s9eCMGyMsGnpDR8EDNOyuGy7Y3l7okX8Xqu464gMp9E+hX7bHkcD6a4xfyIgJcWxsku0tm1TH1dpn4M1UXRuyZZif8P08nuE6MTUL67sAR9J1lpn4lVEL4kflk0pP2tZ5ncgPQFafJrRz05krMb0eU5tb2H4gs7ao/LL6idWo8MM9K1yr8lIuT5x2WW5CX+RjA+i50ex114V6vX3PNP5oVyt+DynTUB9QmXzVm3oLfDc3Cae1uqh7X0CFd+xiztJBtg0VtJaD/xUJcuWfY4cV2lERo9fRrykltzlJqiXHO4nowt8OtN0BcViVV8NJhPhYFzyb4ympxpOlTjm3GETuT2TYhUqdgS9nzleEAbOmOHZdIO2COunPE=
lecoffreBack:
serviceAccountName: lecoffre-back-sa
@ -30,7 +30,7 @@ lecoffreBack:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
env:
- key: .env
scwID: "id:430001f8-68ab-47b2-92e8-38024c35a80d"
scwID: "id:2be9510b-bb1f-4fbe-ab3e-3dc11fb49051"
lecoffreCron:
serviceAccountName: lecoffre-cron-sa
@ -50,5 +50,5 @@ lecoffreCron:
# key is name of the environment variable, scwID is the secret ID in SCW with "id:" in front
env:
- key: .env
scwID: "id:430001f8-68ab-47b2-92e8-38024c35a80d"
scwID: "id:2be9510b-bb1f-4fbe-ab3e-3dc11fb49051"

View File

@ -1,6 +1,6 @@
dockerPullSecret: docker-pull-secret
scwSecretKey: AgChoEnPitXp4Ny/rVMEcevaWKNVpyj2cJYAcq+yFqKwVwnLB+ffDvwqz9XBHu+6d4Nyyjkf37zUAMoaM21lEDWA7x3zfG2/D/j+rvX1qxzZgLD0mjBk7fGElVm332I6JA83oInes8AMMYEDPLElzHnpKRb9KtkIP4NzgOcCeW0ijft3N7Vroez6LEHsBPCA1I9XjKSkGEDvrO0MhWX3iJOlfz+SPMfJAV7rPawOs0ZmohTHrPW8qIvGDn8HCzKyU8zRBoMt+Ogpf5pH4U3JryEFuqD61KAQgablAM8edPIvsgNno9HAEuC2QtRLYA9aUhuKdaKuS58c9P2E80PHWXIlbpFCg6EugQTgNfnYp+3qDUNz8edeCfapYLvF4s9eCMGyMsGnpDR8EDNOyuGy7Y3l7okX8Xqu464gMp9E+hX7bHkcD6a4xfyIgJcWxsku0tm1TH1dpn4M1UXRuyZZif8P08nuE6MTUL67sAR9J1lpn4lVEL4kflk0pP2tZ5ncgPQFafJrRz05krMb0eU5tb2H4gs7ao/LL6idWo8MM9K1yr8lIuT5x2WW5CX+RjA+i50ex114V6vX3PNP5oVyt+DynTUB9QmXzVm3oLfDc3Cae1uqh7X0CFd+xiztJBtg0VtJaD/xUJcuWfY4cV2lERo9fRrykltzlJqiXHO4nowt8OtN0BcViVV8NJhPhYFzyb4ympxpOlTjm3GETuT2TYhUqdgS9nzleEAbOmOHZdIO2COunPE=
scwSecretKey: 59bcf27d-bee3-4d14-8b4d-03fd6a8be6cd
lecoffreBack:
serviceAccountName: lecoffre-back-sa
@ -52,5 +52,3 @@ lecoffreCron:
env:
- key: .env
scwID: "id:2be9510b-bb1f-4fbe-ab3e-3dc11fb49051"

View File

@ -19,10 +19,10 @@ lecoffreBack:
limits:
memory: 2Gi
ingress:
host: api.stg.lecoffre.smart-chain.fr
host: api.ppd.lecoffre.smart-chain.fr
tls:
hosts:
- api.stg.lecoffre.smart-chain.fr
- api.ppd.lecoffre.smart-chain.fr
secretName: api-tls
annotations:
kubernetes.io/ingress.class: nginx

View File

@ -6,6 +6,7 @@ import ApiController from "@Common/system/controller-pattern/ApiController";
@Controller()
@Service()
export default class HomeController extends ApiController {
@Get("/")
protected async get(req: Request, res: Response) {
// const query = processFindManyQuery(req.query);

View File

@ -93,8 +93,6 @@ export default class DocumentsController extends ApiController {
try {
//init Document resource with request body values
const documentEntity = Document.hydrate<Document>(req.body);
console.log(documentEntity);
//validate document
await validateOrReject(documentEntity, { groups: ["createDocument"], forbidUnknownValues: false });

View File

@ -90,7 +90,6 @@ export default class FilesController extends ApiController {
//init File resource with request body values
const fileEntity = File.hydrate<File>(JSON.parse(req.body["q"]));
console.log(fileEntity);
//validate File
// await validateOrReject(fileEntity, { groups: ["createFile"] });

View File

@ -7,7 +7,6 @@ import { JwtPayload } from "jsonwebtoken";
import IdNotService from "@Services/common/IdNotService/IdNotService";
@Controller()
@Service()
export default class UserController extends ApiController {
@ -29,6 +28,10 @@ export default class UserController extends ApiController {
const idNotToken = await this.idNotService.getIdNotToken(code);
const user = await this.idNotService.getOrCreateUser(idNotToken);
if(!user) {
this.httpUnauthorized(response);
return;
}
await this.idNotService.updateUser(user.uid);
await this.idNotService.updateOffice(user.office_uid);
@ -36,7 +39,7 @@ export default class UserController extends ApiController {
const accessToken = this.authService.generateAccessToken(payload);
const refreshToken = this.authService.generateRefreshToken(payload);
this.httpSuccess(response, {accessToken, refreshToken});
this.httpSuccess(response, { accessToken, refreshToken });
} catch (error) {
console.log(error);
this.httpInternalError(response);
@ -77,7 +80,7 @@ export default class UserController extends ApiController {
let accessToken;
this.authService.verifyRefreshToken(token, (err, userPayload) => {
if (err) {
console.log(err)
console.log(err);
this.httpUnauthorized(response);
return;
}
@ -89,7 +92,7 @@ export default class UserController extends ApiController {
});
//success
this.httpSuccess(response, {accessToken});
this.httpSuccess(response, { accessToken });
} catch (error) {
this.httpInternalError(response);
return;

View File

@ -48,10 +48,12 @@ import VotesController from "./api/super-admin/VotesController";
import LiveVoteController from "./api/super-admin/LiveVoteController";
/**
* @description This allow to declare all controllers used in the application
*/
export default {
start: () => {
Container.get(HomeController);
Container.get(UsersControllerSuperAdmin);

View File

@ -25,6 +25,9 @@ export class BackendVariables {
@IsNotEmpty()
public readonly API_ROOT_URL!: string;
@IsNotEmpty()
public readonly APP_HOST!: string;
@IsOptional()
public readonly APP_LABEL!: string;
@ -91,6 +94,7 @@ export class BackendVariables {
this.DATABASE_NAME = process.env["DATABASE_NAME"]!;
this.DATABASE_URL = process.env["DEV_PRISMA_STUDIO_DB_URL"]!;
this.API_ROOT_URL = process.env["API_ROOT_URL"]!;
this.APP_HOST = process.env["APP_HOST"]!;
this.APP_PORT = process.env["APP_PORT"]!;
this.APP_ROOT_URL = process.env["APP_ROOT_URL"]!;
this.APP_LABEL = process.env["APP_LABEL"]!;
@ -117,7 +121,7 @@ export class BackendVariables {
try {
await validateOrReject(this, validationOptions);
} catch (error: any) {
if (process.env["ENV"] === "dev") {
if (process.env["ENV"] === "dev" || process.env["ENV"] === 'stg') {
throw error;
}
throw new Error("Some env variables are required!");

View File

@ -669,6 +669,18 @@ export default async function main() {
created_at: new Date(),
updated_at: new Date(),
},
{
name: "POST anchors",
label: "Ancrer un dossier",
created_at: new Date(),
updated_at: new Date(),
},
{
name: "GET anchors",
label: "Vérifier l'ancrage un dossier",
created_at: new Date(),
updated_at: new Date(),
},
{
name: "POST deed-types",
label: "Création des types d'actes",
@ -756,14 +768,14 @@ export default async function main() {
label: "Administrateur",
created_at: new Date(),
updated_at: new Date(),
rules: rules.slice(0, 33),
rules: rules.slice(0, 35),
},
{
name: "notary",
label: "Notaire",
created_at: new Date(),
updated_at: new Date(),
rules: rules.slice(0, 23),
rules: rules.slice(0, 25),
},
{
name: "default",
@ -780,7 +792,7 @@ export default async function main() {
created_at: new Date(),
updated_at: new Date(),
office: offices[0]!,
rules: rules.slice(0, 33),
rules: rules.slice(0, 35),
},
{
name: "Collaborateur",

View File

@ -45,6 +45,34 @@ export default class EmailBuilder {
nbTrySend: null,
lastTrySendDate: null,
});
}
public async sendRecapEmails(usersToEmail: [{email: string, civility: string, last_name: string}]){
usersToEmail.forEach(user => {
const to = user.email;
const templateVariables = {
civility: user.civility,
last_name: user.last_name,
link: "http://localhost:3000"
};
const templateName = ETemplates.DOCUMENT_RECAP;
const subject = "Récapitulatif hebdromadaire";
this.mailchimpService.create({
templateName,
to,
subject,
templateVariables,
uid: "",
from: null,
cc: [],
cci: [],
sentAt: null,
nbTrySend: null,
lastTrySendDate: null,
});
});
}
}

View File

@ -1,4 +1,5 @@
export const ETemplates = {
DOCUMENT_ASKED: "DOCUMENT_ASKED",
DOCUMENT_REFUSED: "DOCUMENT_REFUSED",
DOCUMENT_RECAP: "DOCUMENT_RECAP",
};

View File

@ -21,6 +21,7 @@ export default class DocumentsRepository extends BaseRepository {
* @description : Find many documents
*/
public async findMany(query: Prisma.DocumentsFindManyArgs) {
query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows);
return this.model.findMany(query);
}

View File

@ -1,17 +1,22 @@
import "module-alias/register";
import "reflect-metadata";
import { Container } from "typedi";
import { BackendVariables } from "@Common/config/variables/Variables";
import CronService from "@Services/common/CronService/CronService";
(async () => {
console.log("Cron started");
try {
const variables = await Container.get(BackendVariables).validate();
Container.get(CronService).archiveFiles();
await Container.get(CronService).updateUsers();
if(variables.ENV !== "dev"){
Container.get(CronService).sendMails();
Container.get(CronService).sendRecapMails();
}
} catch (e) {
console.error(e);
}

View File

@ -84,11 +84,11 @@ export default class AuthService extends BaseService {
};
}
public generateAccessToken(user: any): string {
return jwt.sign({ ...user }, this.variables.ACCESS_TOKEN_SECRET, { expiresIn: "15m" });
return jwt.sign({ ...user, iat: Math.floor(Date.now() / 1000)}, this.variables.ACCESS_TOKEN_SECRET, { expiresIn: "15m" });
}
public generateRefreshToken(user: any): string {
return jwt.sign({ ...user }, this.variables.REFRESH_TOKEN_SECRET, { expiresIn: "1h" });
return jwt.sign({ ...user, iat: Math.floor(Date.now() / 1000)}, this.variables.REFRESH_TOKEN_SECRET, { expiresIn: "1h" });
}
public verifyAccessToken(token: string, callback?: VerifyCallback) {

View File

@ -24,6 +24,21 @@ export default class CronService {
}
}
public async sendRecapMails() {
const cronJob = new CronJob("0 20 * * FRI", async () => { // Every friday at 20:00
try {
await this.mailchimpService.sendRecapEmails();
} catch (e) {
console.error(e);
}
});
// Start job
if (!cronJob.running) {
cronJob.start();
}
}
public async archiveFiles() {
const cronJob = new CronJob("0 0 * * MON", async () => { // Every monday at midnight
try {
@ -39,7 +54,7 @@ export default class CronService {
}
}
public async updateUsers() {
const cronJob = new CronJob("*/15 * * * *", async () => { // Every 15 minutes
const cronJob = new CronJob("0 0 * * *", async () => { // Once a day at midnight
try {
await this.idNotService.updateOffices();
await this.idNotService.updateUsers();

View File

@ -62,6 +62,9 @@ interface IOfficeData {
statutEntite: {
name: string;
};
typeEntite: {
name: string;
}
}
interface IOfficeLocation {
@ -107,13 +110,16 @@ export default class IdNotService extends BaseService {
const query = new URLSearchParams({
client_id: this.variables.IDNOT_CLIENT_ID,
client_secret: this.variables.IDNOT_CLIENT_SECRET,
redirect_uri: `http://0.0.0.0:3000/authorized-client`,
redirect_uri: `${this.variables.APP_HOST}/authorized-client`,
code: code,
grant_type: "authorization_code",
});
console.log(query)
const token = await fetch(this.variables.IDNOT_BASE_URL + this.variables.IDNOT_CONNEXION_URL + "?" + query, { method: "POST" });
const decodedToken = (await token.json()) as IIdNotToken;
console.log(decodedToken)
const decodedIdToken = jwt.decode(decodedToken.id_token) as IdNotJwtPayload;
return decodedIdToken;
}
@ -165,19 +171,44 @@ export default class IdNotService extends BaseService {
const searchParams = new URLSearchParams({
key: this.variables.IDNOT_API_KEY,
});
const userRawData = await (await fetch(
let userData = await (await fetch(
`${this.variables.IDNOT_API_BASE_URL}/api/pp/v2/rattachements/${user.idNot}_${user.office_membership!.idNot}?` +
searchParams,
{
method: "GET",
},
)).json() as IRattachementData;
if (!userData.statutDuRattachement) {
const rattachements = await (await fetch(
`${this.variables.IDNOT_API_BASE_URL}/api/pp/v2/personnes/${user.idNot}/rattachements?` +
searchParams,
{
method: "GET",
},
)).json() as any;
if (userRawData.totalResultCount === 0) {
if (rattachements.totalResultCount === 0) {
await this.userService.updateCheckedAt(user.uid!);
//await this.userService.delete(user.uid!);
return;
}
const userData = userRawData.result[0] as IRattachementData;
const rattachementsResults = rattachements.result as IRattachementData[];
if(!rattachementsResults) return;
rattachementsResults.forEach(async (rattachement) => {
if (rattachement.statutDuRattachement) {
const officeData = await (await fetch(
`${this.variables.IDNOT_API_BASE_URL + rattachement.entiteUrl}?` +
searchParams,
{
method: "GET",
},
)).json() as IOfficeData;
if(officeData.typeEntite.name === "office") {
userData = rattachement;
}
}
});
}
const roleFromIdNot = await this.getRole(userData.typeLien.name);
let updates = 0;
@ -186,24 +217,22 @@ export default class IdNotService extends BaseService {
user.role = roleFromIdNot;
}
if (user.office_membership!.idNot !== userData.entiteUrl.split("/")[5]!) {
if (user.office_membership!.idNot !== userData.entite.ou) {
updates++;
let officeData = (await this.officeService.get({ where: { idNot: userData.entiteUrl.split("/")[5]! } }))[0];
let officeData = (await this.officeService.get({ where: { idNot:userData.entite.ou } }))[0];
if (!officeData) {
const officeIdNotData = (await (
await fetch(`${this.variables.IDNOT_API_BASE_URL + userData.entiteUrl}?` + searchParams, { method: "GET" })
).json()) as IOfficeData;
const officeLocationData = (await (
await fetch(`${this.variables.IDNOT_API_BASE_URL + userData.entite.locationsUrl}?` + searchParams, { method: "GET" })
).json()) as IOfficeLocation;
const office = {
idNot: userData.entiteUrl.split("/")[5]!,
name: officeIdNotData.denominationSociale
? officeIdNotData.denominationSociale
: `office ${userData.entiteUrl.split("/")[5]!}`,
crpcen: officeIdNotData.codeCrpcen,
office_status: this.getOfficeStatus(officeIdNotData.statutEntite.name),
idNot: userData.entite.ou,
name: userData.entite.denominationSociale,
crpcen: userData.entite.codeCrpcen,
office_status: this.getOfficeStatus(userData.entite.statutEntite.name),
address: {
address: officeIdNotData.departementResidence[0]!.libelle, //officeLocationData.result[0]!.adrPostale4,
city: "city", //officeLocationData.result[0]!.adrPostaleVille,
zip_code: Number(officeIdNotData.departementResidence[0]!.code),
address: officeLocationData.result[0]!.adrGeo4,
city: officeLocationData.result[0]!.adrGeoVille.split(" ")[0] ?? officeLocationData.result[0]!.adrGeoVille,
zip_code: Number(officeLocationData.result[0]!.adrGeoCodePostal),
created_at: null,
updated_at: null,
},
@ -229,6 +258,7 @@ export default class IdNotService extends BaseService {
public async updateOffice(officeId: string) {
const officeInfos = await this.officeService.getByUid(officeId);
console.log(officeInfos)
const office = Office.hydrate<Office>(officeInfos!);
const searchParams = new URLSearchParams({
key: this.variables.IDNOT_API_KEY,
@ -246,6 +276,7 @@ export default class IdNotService extends BaseService {
return;
}
const officeData = (await officeRawData.json()) as IOfficeData;
console.log(officeData);
let updates = 0;
if(office.name !== officeData.denominationSociale) {
updates++;
@ -272,10 +303,19 @@ export default class IdNotService extends BaseService {
})
).json()) as IRattachementData;
if(!userData.statutDuRattachement || userData.entite.typeEntite.name !== "office") {
return null;
}
const officeLocationData = (await (
await fetch(`${this.variables.IDNOT_API_BASE_URL + userData.entite.locationsUrl}?` + searchParams, { method: "GET" })
).json()) as IOfficeLocation;
// if(officeLocationData.result[0]!.adrGeoCodePostal.slice(0,2) !== "35") {
// return null;
// }
const role = await this.getRole(userData.typeLien.name);
const userToAdd = {

View File

@ -1,9 +1,10 @@
import EmailRepository from "@Repositories/EmailRepository";
import BaseService from "@Services/BaseService";
import { Emails } from "@prisma/client";
import { Emails, PrismaClient } from "@prisma/client";
import { Service } from "typedi";
import MailchimpClient from "@mailchimp/mailchimp_transactional";
import { BackendVariables } from "@Common/config/variables/Variables";
// import DocumentsService from "@Services/super-admin/DocumentsService/DocumentsService";
@Service()
export default class MailchimpService extends BaseService {
@ -113,4 +114,17 @@ export default class MailchimpService extends BaseService {
};
});
}
public async sendRecapEmails() {
const prisma = new PrismaClient();
const usersToEmail = await prisma.$queryRaw
`SELECT DISTINCT c.email, c.civility, c.last_name
FROM Contacts c
JOIN Users u ON c.uid = u.contact_uid
JOIN office_folders of ON u.office_uid = of.office_uid
JOIN Documents d ON of.uid = d.folder_uid
WHERE d.document_status = 'DEPOSITED';`
console.log(usersToEmail);
}
}