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

rework gateways #331

Merged
merged 1 commit into from
Jun 23, 2023
Merged
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
4 changes: 2 additions & 2 deletions back/src/_testBuilders/buildTestApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { InMemoryAddressGateway } from "../adapters/secondary/addressGateway/InM
import { BasicEventCrawler } from "../adapters/secondary/core/EventCrawlerImplementations";
import { CustomTimeGateway } from "../adapters/secondary/core/TimeGateway/CustomTimeGateway";
import { StubDashboardGateway } from "../adapters/secondary/dashboardGateway/StubDashboardGateway";
import { NotImplementedDocumentGateway } from "../adapters/secondary/documentGateway/NotImplementedDocumentGateway";
import { InMemoryEmailValidationGateway } from "../adapters/secondary/emailValidationGateway/InMemoryEmailValidationGateway";
import { InMemoryLaBonneBoiteAPI } from "../adapters/secondary/immersionOffer/laBonneBoite/InMemoryLaBonneBoiteAPI";
import { InMemoryPassEmploiGateway } from "../adapters/secondary/immersionOffer/passEmploi/InMemoryPassEmploiGateway";
import { InMemoryPoleEmploiGateway } from "../adapters/secondary/immersionOffer/poleEmploi/InMemoryPoleEmploiGateway";
import { InMemoryInclusionConnectGateway } from "../adapters/secondary/InclusionConnectGateway/InMemoryInclusionConnectGateway";
import type { InMemoryNotificationGateway } from "../adapters/secondary/notificationGateway/InMemoryNotificationGateway";
import { NotImplementedDocumentGateway } from "../adapters/secondary/NotImplementedDocumentGateway";
import { InMemoryPeConnectGateway } from "../adapters/secondary/PeConnectGateway/InMemoryPeConnectGateway";
import { InMemoryPoleEmploiGateway } from "../adapters/secondary/poleEmploi/InMemoryPoleEmploiGateway";
import { DeterministShortLinkIdGeneratorGateway } from "../adapters/secondary/shortLinkIdGeneratorGateway/DeterministShortLinkIdGeneratorGateway";
import { InMemorySiretGateway } from "../adapters/secondary/siret/InMemorySiretGateway";
import {
Expand Down
6 changes: 2 additions & 4 deletions back/src/adapters/primary/config/appConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
} from "shared";
import { DomainTopic } from "../../../domain/core/eventBus/events";
import { InclusionConnectConfig } from "../../../domain/inclusionConnect/useCases/InitiateInclusionConnect";
import { S3Params } from "../../secondary/documentGateway/S3DocumentGateway";
import { EmailableApiKey } from "../../secondary/emailValidationGateway/EmailableEmailValidationGateway.dto";
import { S3Params } from "../../secondary/S3DocumentGateway";

export type AccessTokenConfig = {
immersionFacileBaseUrl: AbsoluteUrl;
Expand Down Expand Up @@ -296,9 +296,7 @@ export class AppConfig {
peAuthCandidatUrl: this.peAuthCandidatUrl,
peEnterpriseUrl: this.peEnterpriseUrl,
clientId: this.poleEmploiClientId,
clientSecret: this.throwIfNotDefinedOrDefault(
"POLE_EMPLOI_CLIENT_SECRET",
),
clientSecret: this.poleEmploiClientSecret,
};
}

Expand Down
56 changes: 25 additions & 31 deletions back/src/adapters/primary/config/createGateways.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
immersionFacileContactEmail,
pipeWithValue,
} from "shared";
import { GetAccessTokenResponse } from "../../../domain/convention/ports/PoleEmploiGateway";
import { noRetries } from "../../../domain/core/ports/RetryStrategy";
import { TimeGateway } from "../../../domain/core/ports/TimeGateway";
import { DashboardGateway } from "../../../domain/dashboard/port/DashboardGateway";
Expand All @@ -15,35 +16,33 @@ import { createLogger } from "../../../utils/logger";
import { HttpAddressGateway } from "../../secondary/addressGateway/HttpAddressGateway";
import { addressesExternalTargets } from "../../secondary/addressGateway/HttpAddressGateway.targets";
import { InMemoryAddressGateway } from "../../secondary/addressGateway/InMemoryAddressGateway";
import { CachingAccessTokenGateway } from "../../secondary/core/CachingAccessTokenGateway";
import { InMemoryCachingGateway } from "../../secondary/core/InMemoryCachingGateway";
import { CustomTimeGateway } from "../../secondary/core/TimeGateway/CustomTimeGateway";
import { RealTimeGateway } from "../../secondary/core/TimeGateway/RealTimeGateway";
import { MetabaseDashboardGateway } from "../../secondary/dashboardGateway/MetabaseDashboardGateway";
import { StubDashboardGateway } from "../../secondary/dashboardGateway/StubDashboardGateway";
import { NotImplementedDocumentGateway } from "../../secondary/documentGateway/NotImplementedDocumentGateway";
import { S3DocumentGateway } from "../../secondary/documentGateway/S3DocumentGateway";
import { EmailableEmailValidationGateway } from "../../secondary/emailValidationGateway/EmailableEmailValidationGateway";
import { emailableValidationTargets } from "../../secondary/emailValidationGateway/EmailableEmailValidationGateway.targets";
import { InMemoryEmailValidationGateway } from "../../secondary/emailValidationGateway/InMemoryEmailValidationGateway";
import { InMemoryAccessTokenGateway } from "../../secondary/immersionOffer/InMemoryAccessTokenGateway";
import { HttpLaBonneBoiteAPI } from "../../secondary/immersionOffer/laBonneBoite/HttpLaBonneBoiteAPI";
import { InMemoryLaBonneBoiteAPI } from "../../secondary/immersionOffer/laBonneBoite/InMemoryLaBonneBoiteAPI";
import { createLbbTargets } from "../../secondary/immersionOffer/laBonneBoite/LaBonneBoiteTargets";
import { HttpPassEmploiGateway } from "../../secondary/immersionOffer/passEmploi/HttpPassEmploiGateway";
import { InMemoryPassEmploiGateway } from "../../secondary/immersionOffer/passEmploi/InMemoryPassEmploiGateway";
import { HttpPoleEmploiGateway } from "../../secondary/immersionOffer/poleEmploi/HttpPoleEmploiGateway";
import { InMemoryPoleEmploiGateway } from "../../secondary/immersionOffer/poleEmploi/InMemoryPoleEmploiGateway";
import { createPoleEmploiTargets } from "../../secondary/immersionOffer/poleEmploi/PoleEmploi.targets";
import { PoleEmploiAccessTokenGateway } from "../../secondary/immersionOffer/PoleEmploiAccessTokenGateway";
import { HttpInclusionConnectGateway } from "../../secondary/InclusionConnectGateway/HttpInclusionConnectGateway";
import { makeInclusionConnectExternalTargets } from "../../secondary/InclusionConnectGateway/inclusionConnectExternal.targets";
import { InMemoryInclusionConnectGateway } from "../../secondary/InclusionConnectGateway/InMemoryInclusionConnectGateway";
import { BrevoNotificationGateway } from "../../secondary/notificationGateway/BrevoNotificationGateway";
import { brevoNotificationGatewayTargets } from "../../secondary/notificationGateway/BrevoNotificationGateway.targets";
import { InMemoryNotificationGateway } from "../../secondary/notificationGateway/InMemoryNotificationGateway";
import { NotImplementedDocumentGateway } from "../../secondary/NotImplementedDocumentGateway";
import { HttpPeConnectGateway } from "../../secondary/PeConnectGateway/HttpPeConnectGateway";
import { InMemoryPeConnectGateway } from "../../secondary/PeConnectGateway/InMemoryPeConnectGateway";
import { makePeConnectExternalTargets } from "../../secondary/PeConnectGateway/peConnectApi.targets";
import { S3DocumentGateway } from "../../secondary/S3DocumentGateway";
import { HttpPoleEmploiGateway } from "../../secondary/poleEmploi/HttpPoleEmploiGateway";
import { InMemoryPoleEmploiGateway } from "../../secondary/poleEmploi/InMemoryPoleEmploiGateway";
import { createPoleEmploiTargets } from "../../secondary/poleEmploi/PoleEmploi.targets";
import { DeterministShortLinkIdGeneratorGateway } from "../../secondary/shortLinkIdGeneratorGateway/DeterministShortLinkIdGeneratorGateway";
import { NanoIdShortLinkIdGeneratorGateway } from "../../secondary/shortLinkIdGeneratorGateway/NanoIdShortLinkIdGeneratorGateway";
import { AnnuaireDesEntreprisesSiretGateway } from "../../secondary/siret/AnnuaireDesEntreprisesSiretGateway";
Expand Down Expand Up @@ -95,23 +94,27 @@ export const createGateways = async (config: AppConfig) => {
apiAddress: config.apiAddress,
});

const cachingAccessTokenGateway = [
config.laBonneBoiteGateway,
config.poleEmploiGateway,
].includes("HTTPS")
? new CachingAccessTokenGateway(
new PoleEmploiAccessTokenGateway(
config.poleEmploiAccessTokenConfig,
noRetries,
),
)
: new InMemoryAccessTokenGateway();

const timeGateway =
config.timeGateway === "CUSTOM"
? new CustomTimeGateway()
: new RealTimeGateway();

const poleEmploiGateway =
config.poleEmploiGateway === "HTTPS"
? new HttpPoleEmploiGateway(
configureCreateHttpClientForExternalApi(
axios.create({ timeout: config.externalAxiosTimeout }),
)(createPoleEmploiTargets(config.peApiUrl)),
new InMemoryCachingGateway<GetAccessTokenResponse>(
timeGateway,
"expires_in",
),
config.peApiUrl,
config.poleEmploiAccessTokenConfig,
noRetries,
)
: new InMemoryPoleEmploiGateway();

return {
addressApi: createAddressGateway(config),
dashboardGateway: createDashboardGateway(config),
Expand All @@ -128,7 +131,7 @@ export const createGateways = async (config: AppConfig) => {
timeout: config.externalAxiosTimeout,
}),
)(createLbbTargets(config.peApiUrl)),
cachingAccessTokenGateway,
poleEmploiGateway,
config.poleEmploiClientId,
)
: new InMemoryLaBonneBoiteAPI(),
Expand All @@ -137,16 +140,7 @@ export const createGateways = async (config: AppConfig) => {
? new HttpPassEmploiGateway(config.passEmploiUrl, config.passEmploiKey)
: new InMemoryPassEmploiGateway(),
peConnectGateway: createPoleEmploiConnectGateway(config),
poleEmploiGateway:
config.poleEmploiGateway === "HTTPS"
? new HttpPoleEmploiGateway(
configureCreateHttpClientForExternalApi(
axios.create({ timeout: config.externalAxiosTimeout }),
)(createPoleEmploiTargets(config.peApiUrl)),
config.peApiUrl,
cachingAccessTokenGateway,
)
: new InMemoryPoleEmploiGateway(),
poleEmploiGateway,
timeGateway,
siret: getSiretGateway(config.siretGateway, config, timeGateway),
shortLinkGenerator:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
import axios from "axios";
import { Pool } from "pg";
import { GetAccessTokenResponse } from "../../../domain/convention/ports/PoleEmploiGateway";
import { UpdateAllPeAgencies } from "../../../domain/convention/useCases/agencies/UpdateAllPeAgencies";
import { noRetries } from "../../../domain/core/ports/RetryStrategy";
import { HttpAddressGateway } from "../../secondary/addressGateway/HttpAddressGateway";
import { addressesExternalTargets } from "../../secondary/addressGateway/HttpAddressGateway.targets";
import { ConsoleAppLogger } from "../../secondary/core/ConsoleAppLogger";
import { InMemoryCachingGateway } from "../../secondary/core/InMemoryCachingGateway";
import { RealTimeGateway } from "../../secondary/core/TimeGateway/RealTimeGateway";
import { UuidV4Generator } from "../../secondary/core/UuidGeneratorImplementations";
import { HttpPeAgenciesReferential } from "../../secondary/immersionOffer/peAgenciesReferential/HttpPeAgenciesReferential";
import { PoleEmploiAccessTokenGateway } from "../../secondary/immersionOffer/PoleEmploiAccessTokenGateway";
import { HttpPoleEmploiGateway } from "../../secondary/poleEmploi/HttpPoleEmploiGateway";
import { createPoleEmploiTargets } from "../../secondary/poleEmploi/PoleEmploi.targets";
import { AppConfig } from "../config/appConfig";
import { configureCreateHttpClientForExternalApi } from "../config/createHttpClientForExternalApi";
import { createUowPerformer } from "../config/uowConfig";

const updateAllPeAgenciesScript = async () => {
const config = AppConfig.createFromEnv();
const accessTokenGateway = new PoleEmploiAccessTokenGateway(
config.poleEmploiAccessTokenConfig,
noRetries,
);

const httpPeAgenciesReferential = new HttpPeAgenciesReferential(
config.peApiUrl,
accessTokenGateway,
new HttpPoleEmploiGateway(
configureCreateHttpClientForExternalApi(
axios.create({ timeout: config.externalAxiosTimeout }),
)(createPoleEmploiTargets(config.peApiUrl)),
new InMemoryCachingGateway<GetAccessTokenResponse>(
new RealTimeGateway(),
"expires_in",
),
config.peApiUrl,
config.poleEmploiAccessTokenConfig,
noRetries,
),
config.poleEmploiClientId,
);

Expand Down
62 changes: 0 additions & 62 deletions back/src/adapters/secondary/core/CachingAccessTokenGateway.ts

This file was deleted.

52 changes: 52 additions & 0 deletions back/src/adapters/secondary/core/InMemoryCachingGateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { addSeconds } from "date-fns";
import isAfter from "date-fns/isAfter";
import { TimeGateway } from "../../../domain/core/ports/TimeGateway";
import { RealTimeGateway } from "./TimeGateway/RealTimeGateway";

export class InMemoryCachingGateway<T> {
public constructor(
private readonly timeGateway: TimeGateway = new RealTimeGateway(),
private responseExpireInSecondsProp: keyof T,
) {}

public async caching(
value: string,
onCacheMiss: () => Promise<T>,
): Promise<T> {
const cache = this.cache[value];
return cache === undefined || this.isExpired(await cache)
? this.onBadCache(value, onCacheMiss)
: (await cache).response;
}

private onBadCache(value: string, onCacheMiss: () => Promise<T>): Promise<T> {
this.cache[value] = this.refreshCache(onCacheMiss);
return this.caching(value, onCacheMiss);
}
bbohec marked this conversation as resolved.
Show resolved Hide resolved

private async refreshCache(
onCacheMiss: () => Promise<T>,
): Promise<CacheEntry<T>> {
const response = await onCacheMiss();
return {
response,
expirationTime: addSeconds(
this.timeGateway.now(),
Number(response[this.responseExpireInSecondsProp]) -
this.minimumCacheLifetime || 0,
),
};
}

private isExpired(entry: CacheEntry<T>): boolean {
return isAfter(this.timeGateway.now(), entry.expirationTime);
}

private readonly cache: Partial<Record<string, Promise<CacheEntry<T>>>> = {};
bbohec marked this conversation as resolved.
Show resolved Hide resolved
private readonly minimumCacheLifetime = 30;
}

type CacheEntry<T> = {
response: T;
expirationTime: Date;
};
Loading