Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Afficher les bannières dynamiquement. #11071

Open
wants to merge 7 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions api/banner/scripts/add.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Script } from '../../src/shared/application/scripts/script.js';
import { ScriptRunner } from '../../src/shared/application/scripts/script-runner.js';
import { informationBannersStorage } from '../../src/shared/infrastructure/key-value-storages/index.js';

export class AddInformationBanners extends Script {
constructor() {
super({
description: 'Add information banners data to Redis',
permanent: true,
options: {
target: {
type: 'string',
describe: 'application name we want to add information banners to',
required: true,
requiresArg: true,
},
severity: {
type: 'string',
describe: 'severity of the message',
choices: ['error', 'warning', 'information'],
required: true,
requiresArg: true,
},
message_fr: {
type: 'string',
describe: 'message content in French',
required: true,
requiresArg: true,
},
message_en: {
type: 'string',
describe: 'message content in English',
required: true,
requiresArg: true,
},
},
});
}

async handle({ options }) {
const { target, severity, message_fr, message_en } = options;
const banners = (await informationBannersStorage.get(target)) ?? [];

banners.push({ severity, message: `[fr]${message_fr}[/fr][en]${message_en}[/en]` });

await informationBannersStorage.save({ key: target, value: banners });
}
}

await ScriptRunner.execute(import.meta.url, AddInformationBanners);
28 changes: 28 additions & 0 deletions api/banner/scripts/remove.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Script } from '../../src/shared/application/scripts/script.js';
import { ScriptRunner } from '../../src/shared/application/scripts/script-runner.js';
import { informationBannersStorage } from '../../src/shared/infrastructure/key-value-storages/index.js';

export class RemoveInformationBanners extends Script {
constructor() {
super({
description: 'Remove information banners data from Redis',
permanent: true,
options: {
target: {
type: 'string',
describe: 'application name we want to remove information banners from',
required: true,
requiresArg: true,
},
Comment on lines +11 to +16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion(non-bloquante) : Il manque peut-être de pouvoir choisir le type d'alerte à retirer : Si on a une alerte information pour plusieurs mois et qu'on ajoute une alerte warning, là on retire les deux pour ajouter de nouveau celle de type information, ce qui peut être sujet à faire des erreurs ou ne pas la remettre.

question : On parle de Pix Exploit dans la description, du coup est-ce que ces scripts ne sont pas des doublons ? 🤔

Copy link
Contributor

@bpetetot bpetetot Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je pense que Pix Exploit est uniquement pour la production (pour le moment), les scripts permettent à n'importe qui de réaliser les opérations en local/RA/Integration/Recette

},
});
}

async handle({ options }) {
const { target } = options;

await informationBannersStorage.delete(target);
}
}

await ScriptRunner.execute(import.meta.url, RemoveInformationBanners);
2 changes: 1 addition & 1 deletion api/db/seeds/data/team-prescription/build-quests.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ATTESTATIONS } from '../../../../src/profile/domain/constants.js';
import { REWARD_TYPES } from '../../../../src/quest/domain/constants.js';
import { COMPARISON } from '../../../../src/quest/domain/models/Quest.js';
import { Assessment, CampaignParticipationStatuses, Membership } from '../../../../src/shared/domain/models/index.js';
import { temporaryStorage } from '../../../../src/shared/infrastructure/temporary-storage/index.js';
import { temporaryStorage } from '../../../../src/shared/infrastructure/key-value-storages/index.js';
import {
AEFE_TAG,
FEATURE_ATTESTATIONS_MANAGEMENT_ID,
Expand Down
12 changes: 7 additions & 5 deletions api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { disconnect, prepareDatabaseConnection } from './db/knex-database-connec
import { createServer } from './server.js';
import { config } from './src/shared/config.js';
import { learningContentCache } from './src/shared/infrastructure/caches/learning-content-cache.js';
import { temporaryStorage } from './src/shared/infrastructure/temporary-storage/index.js';
import { informationBannersStorage, temporaryStorage } from './src/shared/infrastructure/key-value-storages/index.js';
import { logger } from './src/shared/infrastructure/utils/logger.js';
import { redisMonitor } from './src/shared/infrastructure/utils/redis-monitor.js';

Expand Down Expand Up @@ -40,13 +40,15 @@ async function _exitOnSignal(signal) {
logger.info('Stopping HAPI Oppsy server...');
await server.oppsy.stop();
}
logger.info('Closing connexions to database...');
logger.info('Closing connections to database...');
await disconnect();
logger.info('Closing connexions to cache...');
logger.info('Closing connections to cache...');
await learningContentCache.quit();
logger.info('Closing connexions to temporary storage...');
logger.info('Closing connections to temporary storage...');
await temporaryStorage.quit();
logger.info('Closing connexions to redis monitor...');
logger.info('Closing connections to information banners storage...');
await informationBannersStorage.quit();
logger.info('Closing connections to redis monitor...');
await redisMonitor.quit();
logger.info('Exiting process...');
}
Expand Down
2 changes: 2 additions & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@
"scripts": {
"clean": "rm -rf node_modules",
"cache:refresh": "node scripts/refresh-cache",
"banner:add": "node banner/scripts/add.js",
"banner:remove": "node banner/scripts/remove.js",
"datamart:create": "node scripts/datamart/create-datamart",
"datamart:delete": "node scripts/datamart/drop-datamart",
"datamart:new-migration": "npx knex --knexfile datamart/knexfile.js migrate:make --stub $PWD/db/template.js $migrationname",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import * as targetProfileShareRepository from '../lib/infrastructure/repositorie
import * as dataProtectionOfficerRepository from '../src/organizational-entities/infrastructure/repositories/data-protection-officer.repository.js';
import * as organizationTagRepository from '../src/organizational-entities/infrastructure/repositories/organization-tag.repository.js';
import { tagRepository } from '../src/organizational-entities/infrastructure/repositories/tag.repository.js';
import { informationBannersStorage, temporaryStorage } from '../src/shared/infrastructure/key-value-storages/index.js';
import * as organizationRepository from '../src/shared/infrastructure/repositories/organization-repository.js';
import { temporaryStorage } from '../src/shared/infrastructure/temporary-storage/index.js';
import { organizationInvitationRepository } from '../src/team/infrastructure/repositories/organization-invitation.repository.js';
import { checkCsvHeader, parseCsvWithHeader } from './helpers/csvHelpers.js';

Expand Down Expand Up @@ -137,6 +137,7 @@ async function main() {
// l'import de OidcIdentityProviders dans les validateurs démarre le service redis
// il faut donc stopper le process pour que celui ci s'arrête, il suffit d'avoir l'import du storage pour y avoir accès
temporaryStorage.quit();
informationBannersStorage.quit();
}
}
})();
Expand Down
6 changes: 5 additions & 1 deletion api/scripts/data-generation/generate-certif-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ import { DatabaseBuilder } from '../../db/database-builder/database-builder.js';
import { getNewSessionCode } from '../../src/certification/enrolment/domain/services/session-code-service.js';
import { CampaignParticipationStatuses } from '../../src/shared/domain/models/index.js';
import { learningContentCache } from '../../src/shared/infrastructure/caches/learning-content-cache.js';
import {
informationBannersStorage,
temporaryStorage,
} from '../../src/shared/infrastructure/key-value-storages/index.js';
import * as skillRepository from '../../src/shared/infrastructure/repositories/skill-repository.js';
import { temporaryStorage } from '../../src/shared/infrastructure/temporary-storage/index.js';
import { logger } from '../../src/shared/infrastructure/utils/logger.js';
import { PromiseUtils } from '../../src/shared/infrastructure/utils/promise-utils.js';
import {
Expand Down Expand Up @@ -396,6 +399,7 @@ async function _disconnect() {
logger.info('Closing connexions to cache...');
await learningContentCache.quit();
await temporaryStorage.quit();
await informationBannersStorage.quit();
logger.info('Exiting process gracefully...');
}

Expand Down
2 changes: 2 additions & 0 deletions api/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { setupErrorHandling } from './config/server-setup-error-handling.js';
import { knex } from './db/knex-database-connection.js';
import { authentication } from './lib/infrastructure/authentication.js';
import { routes } from './lib/routes.js';
import { bannerRoutes } from './src/banner/routes.js';
import {
attachTargetProfileRoutes,
complementaryCertificationRoutes,
Expand Down Expand Up @@ -243,6 +244,7 @@ const setupRoutesAndPlugins = async function (server) {
...certificationRoutes,
...prescriptionRoutes,
...parcoursupRoutes,
bannerRoutes,
{
name: 'root',
register: async function (server) {
Expand Down
16 changes: 16 additions & 0 deletions api/src/banner/application/banner-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { usecases } from '../domain/usecases/index.js';
import * as informationBannerSerializer from '../infrastructure/serializers/jsonapi/information-banner-serializer.js';

const getInformationBanner = async function (request) {
const { target: id } = request.params;

const informationBanner = await usecases.getInformationBanner({ id });

return informationBannerSerializer.serialize(informationBanner);
};

const bannerController = {
getInformationBanner,
};

export { bannerController };
20 changes: 20 additions & 0 deletions api/src/banner/application/banner-route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { bannerController } from './banner-controller.js';

const register = async function (server) {
server.route([
{
method: 'GET',
path: '/api/information-banners/{target}',
options: {
auth: false,
handler: bannerController.getInformationBanner,
cache: {
expiresIn: 30 * 1000,
},
},
},
]);
};

const name = 'src-banners-api';
export { name, register };
21 changes: 21 additions & 0 deletions api/src/banner/domain/models/information-banner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export class InformationBanner {
constructor({ id, banners }) {
this.id = id;
this.banners =
banners?.map((banner, index) => {
return new Banner({ ...banner, id: `${id}:${index + 1}` });
}) ?? [];
}

static empty({ id }) {
return new InformationBanner({ id });
}
}

class Banner {
constructor({ id, message, severity }) {
this.id = id;
this.message = message;
this.severity = severity;
}
}
5 changes: 5 additions & 0 deletions api/src/banner/domain/usecases/get-information-banner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const getInformationBanner = async ({ id, informationBannerRepository }) => {
return informationBannerRepository.get({ id });
};

export { getInformationBanner };
33 changes: 33 additions & 0 deletions api/src/banner/domain/usecases/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// eslint-disable import/no-restricted-paths
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

import { injectDependencies } from '../../../shared/infrastructure/utils/dependency-injection.js';
import { importNamedExportsFromDirectory } from '../../../shared/infrastructure/utils/import-named-exports-from-directory.js';
import * as informationBannerRepository from '../../infrastructure/repositories/information-banner-repository.js';

/**
*
* Using {@link https://jsdoc.app/tags-type "Closure Compiler's syntax"} to document injected dependencies
*
* @typedef {informationBannerRepository} InformationBannerRepository
**/
const dependencies = {
informationBannerRepository,
};

const path = dirname(fileURLToPath(import.meta.url));

const usecasesWithoutInjectedDependencies = {
...(await importNamedExportsFromDirectory({
path: join(path, './'),
ignoredFileNames: 'index.js',
})),
};

const usecases = injectDependencies(usecasesWithoutInjectedDependencies, dependencies);

/**
* @typedef {dependencies} dependencies
*/
export { usecases };
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { informationBannersStorage } from '../../../shared/infrastructure/key-value-storages/index.js';
import { InformationBanner } from '../../domain/models/information-banner.js';

const get = async function ({ id }) {
const banners = await informationBannersStorage.get(id);
if (!banners) {
return InformationBanner.empty({ id });
}

return new InformationBanner({ id, banners });
};

export { get };
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import jsonapiSerializer from 'jsonapi-serializer';

const { Serializer } = jsonapiSerializer;

const serialize = function (informationBanner) {
return new Serializer('information-banners', {
attributes: ['banners'],
banners: {
included: true,
ref: 'id',
attributes: ['severity', 'message'],
},
}).serialize(informationBanner);
};

export { serialize };
3 changes: 3 additions & 0 deletions api/src/banner/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as bannerRoute from './application/banner-route.js';

export const bannerRoutes = [bannerRoute];
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { config } from '../../../../../src/shared/config.js';
import { temporaryStorage } from '../../../../shared/infrastructure/temporary-storage/index.js';
import { temporaryStorage } from '../../../../shared/infrastructure/key-value-storages/index.js';

const sessionMassImportTemporaryStorage = temporaryStorage.withPrefix('sessions-mass-import:');
import { randomUUID } from 'node:crypto';
Expand All @@ -19,9 +19,7 @@ const save = async function ({ sessions, userId }) {

const getByKeyAndUserId = async function ({ cachedValidatedSessionsKey, userId }) {
const key = `${userId}:${cachedValidatedSessionsKey}`;
const sessions = await sessionMassImportTemporaryStorage.get(key);

return sessions;
return sessionMassImportTemporaryStorage.get(key);
};

const remove = async function ({ cachedValidatedSessionsKey, userId }) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AnswerJob } from '../../../quest/domain/models/AnwserJob.js';
import { config } from '../../../shared/config.js';
import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';
import { temporaryStorage } from '../../../shared/infrastructure/key-value-storages/index.js';
import { JobRepository } from '../../../shared/infrastructure/repositories/jobs/job-repository.js';
import { temporaryStorage } from '../../../shared/infrastructure/temporary-storage/index.js';

const profileRewardTemporaryStorage = temporaryStorage.withPrefix('profile-rewards:');

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { config } from '../../../shared/config.js';
import { temporaryStorage } from '../../../shared/infrastructure/temporary-storage/index.js';
import { temporaryStorage } from '../../../shared/infrastructure/key-value-storages/index.js';
const authenticationSessionTemporaryStorage = temporaryStorage.withPrefix('authentication-session:');

const EXPIRATION_DELAY_SECONDS = config.authenticationSession.temporaryStorage.expirationDelaySeconds;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { OIDC_ERRORS } from '../../../shared/domain/constants.js';
import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';
import { OidcError, OidcMissingFieldsError } from '../../../shared/domain/errors.js';
import { AuthenticationMethod, AuthenticationSessionContent } from '../../../shared/domain/models/index.js';
import { temporaryStorage } from '../../../shared/infrastructure/key-value-storages/index.js';
import { monitoringTools } from '../../../shared/infrastructure/monitoring-tools.js';
import { temporaryStorage } from '../../../shared/infrastructure/temporary-storage/index.js';
import { logger } from '../../../shared/infrastructure/utils/logger.js';
import { DEFAULT_CLAIM_MAPPING } from '../constants/oidc-identity-providers.js';
import { ClaimManager } from '../models/ClaimManager.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import crypto from 'node:crypto';

import { config } from '../../../shared/config.js';
import { temporaryStorage } from '../../../shared/infrastructure/temporary-storage/index.js';
import { temporaryStorage } from '../../../shared/infrastructure/key-value-storages/index.js';

const emailValidationDemandTemporaryStorage = temporaryStorage.withPrefix('email-validation-demand:');

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { temporaryStorage } from '../../../shared/infrastructure/temporary-storage/index.js';
import { temporaryStorage } from '../../../shared/infrastructure/key-value-storages/index.js';
import { PromiseUtils } from '../../../shared/infrastructure/utils/promise-utils.js';
import { RefreshToken } from '../../domain/models/RefreshToken.js';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { config } from '../../../shared/config.js';
import { temporaryStorage } from '../../../shared/infrastructure/temporary-storage/index.js';
import { temporaryStorage } from '../../../shared/infrastructure/key-value-storages/index.js';
import { EmailModificationDemand } from '../../domain/models/EmailModificationDemand.js';
const verifyEmailTemporaryStorage = temporaryStorage.withPrefix('verify-email:');

Expand Down
2 changes: 1 addition & 1 deletion api/src/quest/application/jobs/answer-job-controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { JobController, JobGroup } from '../../../shared/application/jobs/job-controller.js';
import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';
import { temporaryStorage } from '../../../shared/infrastructure/temporary-storage/index.js';
import { temporaryStorage } from '../../../shared/infrastructure/key-value-storages/index.js';
import { AnswerJob } from '../../domain/models/AnwserJob.js';
import { usecases } from '../../domain/usecases/index.js';

Expand Down
Loading
Loading