Skip to content

Commit

Permalink
write first kysely query to get compiled data private
Browse files Browse the repository at this point in the history
  • Loading branch information
JeromeBu committed Jul 5, 2024
1 parent 8d1cf86 commit 6d63d80
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 36 deletions.
172 changes: 172 additions & 0 deletions api/src/core/adapters/dbApi/kysely/createPgDbApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { Kysely } from "kysely";
import { CompiledData } from "../../../ports/CompileData";
import { SoftwareExternalData } from "../../../ports/GetSoftwareExternalData";
import { ServiceProvider } from "../../../usecases/readWriteSillData";
import { Database } from "./kysely.database";
import { createPgDialect } from "./kysely.dialect";
import { emptyArrayIfNull, jsonAggOrEmptyArray, jsonBuildObject, jsonStripNulls } from "./kysely.utils";

export const createKyselyPgDbApi = (dbUrl: string) => {
const db = new Kysely<Database>({ dialect: createPgDialect(dbUrl) });

return {
// getSoftwareById: async (id: number): Promise<Db.SoftwareRow | undefined> => {
// const result = await db.selectFrom("softwares").selectAll().where("id", "=", id).executeTakeFirst();
// if (!result) return;
//
// return {
// ...result,
// parentSoftwareWikidataId: result?.parentSoftwareWikidataId ?? undefined,
// dereferencing: result?.dereferencing ?? undefined,
// externalId: result?.externalId ?? undefined,
// externalDataOrigin: result?.externalDataOrigin ?? "wikidata",
// comptoirDuLibreId: result?.comptoirDuLibreId ?? undefined,
// catalogNumeriqueGouvFrId: result?.catalogNumeriqueGouvFrId ?? undefined,
// generalInfoMd: result?.generalInfoMd ?? undefined,
// logoUrl: result?.logoUrl ?? undefined
// };
// },
getCompiledDataPrivate: (): Promise<CompiledData<"private">> => {
return db
.selectFrom("softwares as s")
.leftJoin("compiled_softwares as csft", "csft.softwareId", "s.id")
.leftJoin("software_referents as referents", "s.id", "referents.softwareId")
.leftJoin("software_users as users", "s.id", "users.softwareId")
.leftJoin("agents as ar", "referents.agentId", "ar.id")
.leftJoin("agents as au", "referents.agentId", "au.id")
.leftJoin("instances", "s.id", "instances.mainSoftwareSillId")
.select(({ ref, fn }) =>
// jsonStripNulls(
jsonStripNulls(
jsonBuildObject({
addedByAgentEmail: ref("s.addedByAgentEmail"),
annuaireCnllServiceProviders: ref("annuaireCnllServiceProviders"),
catalogNumeriqueGouvFrId: ref("s.catalogNumeriqueGouvFrId"),
categories: ref("s.categories"),
comptoirDuLibreSoftware: ref("csft.comptoirDuLibreSoftware"),
dereferencing: ref("s.dereferencing"),
description: ref("s.description"),
doRespectRgaa: ref("s.doRespectRgaa"),
externalDataOrigin: ref("s.externalDataOrigin"),
externalId: ref("s.externalId"),
generalInfoMd: ref("s.generalInfoMd"),
id: ref("s.id"),
isFromFrenchPublicService: ref("s.isFromFrenchPublicService"),
isPresentInSupportContract: ref("s.isPresentInSupportContract"),
isStillInObservation: ref("s.isStillInObservation"),
keywords: ref("s.keywords"),
latestVersion: ref("csft.latestVersion"),
license: ref("s.license"),
logoUrl: ref("s.logoUrl"),
name: ref("s.name"),
parentWikidataSoftware: ref("csft.parentWikidataSoftware"),
referencedSinceTime: ref("s.referencedSinceTime"),
serviceProviders: emptyArrayIfNull(fn, ref("csft.serviceProviders")).$castTo<
ServiceProvider[]
>(),
similarExternalSoftwares: emptyArrayIfNull(
fn,
ref("csft.similarExternalSoftwares")
).$castTo<CompiledData.SimilarSoftware[]>(),
softwareExternalData: ref("csft.softwareExternalData"),
softwareType: ref("s.softwareType"),
testUrls: ref("s.testUrls"),
updateTime: ref("s.updateTime"),
versionMin: ref("s.versionMin"),
workshopUrls: ref("s.workshopUrls"),
referents: jsonAggOrEmptyArray(
fn,
jsonStripNulls(
jsonBuildObject({
email: ref("ar.email").$castTo<string>(),
organization: ref("ar.organization").$castTo<string>(),
isExpert: ref("referents.isExpert").$castTo<boolean>(),
serviceUrl: ref("referents.serviceUrl"),
useCaseDescription: ref("referents.useCaseDescription").$castTo<string>()
})
)
),
users: jsonAggOrEmptyArray(
fn,
jsonStripNulls(
jsonBuildObject({
os: ref("users.os"),
serviceUrl: ref("users.serviceUrl"),
useCaseDescription: ref("users.useCaseDescription").$castTo<string>(),
version: ref("users.version").$castTo<string>(),
organization: ref("au.organization").$castTo<string>()
})
)
),
instances: jsonAggOrEmptyArray(
fn,
jsonStripNulls(
jsonBuildObject({
id: ref("instances.id").$castTo<number>(),
organization: ref("instances.organization").$castTo<string>(),
targetAudience: ref("instances.targetAudience").$castTo<string>(),
publicUrl: ref("instances.publicUrl"),
otherWikidataSoftwares: ref("instances.otherSoftwareWikidataIds").$castTo<
SoftwareExternalData[]
>(), // todo fetch the corresponding softwares,
addedByAgentEmail: ref("instances.addedByAgentEmail").$castTo<string>()
})
)
)
})
).as("compliedSoftware")
)
.execute()
.then(results =>
results.map(
({ compliedSoftware }): CompiledData.Software<"private"> => ({
...compliedSoftware,
doRespectRgaa: compliedSoftware.doRespectRgaa ?? null
})
)
);
}
};
};

// ----------- common -----------
// annuaireCnllServiceProviders
// catalogNumeriqueGouvFrId
// categories
// comptoirDuLibreSoftware
// dereferencing
// description
// doRespectRgaa
// externalDataOrigin
// externalId
// generalInfoMd
// id
// isFromFrenchPublicService
// isPresentInSupportContract
// isStillInObservation
// keywords
// latestVersion
// license
// logoUrl
// name
// parentWikidataSoftware
// referencedSinceTime
// serviceProviders
// similarExternalSoftwares
// softwareExternalData
// softwareType
// testUrls
// updateTime
// versionMin
// workshopUrls
//
// ----------- private -----------
// addedByAgentEmail
// users
// referents
// instances
//
// ----------- public -----------
// userAndReferentCountByOrganization
// hasExpertReferent
// instances
67 changes: 34 additions & 33 deletions api/src/core/adapters/dbApi/kysely/kysely.database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,43 +97,44 @@ type SoftwaresTable = {

// ---------- compiled data ----------

type ComptoirDuLibreProvider = {
id: number;
url: string;
name: string;
type: string;
external_resources: {
website: string | null;
export namespace PgComptoirDuLibre {
type Provider = {
id: number;
url: string;
name: string;
type: string;
external_resources: {
website: string | null;
};
};
};

type ComptoirDuLibreUser = {
id: number;
url: string;
name: string;
type: string;
external_resources: {
website: string | null;
type User = {
id: number;
url: string;
name: string;
type: string;
external_resources: {
website: string | null;
};
};
};

type ComptoirDuLibreSoftware = {
softwareId: number;
comptoirDuLibreId: number;
logoUrl: string | undefined;
keywords: string[] | undefined;
created: string;
modified: string;
url: string;
name: string;
licence: string;
external_resources: {
website: string | null;
repository: string | null;
export type Software = {
id: number;
logoUrl: string | undefined;
keywords: string[] | undefined;
created: string;
modified: string;
url: string;
name: string;
licence: string;
external_resources: {
website: string | null;
repository: string | null;
};
providers: Provider[];
users: User[];
};
providers: ComptoirDuLibreProvider[];
users: ComptoirDuLibreUser[];
};
}

type ServiceProvider = {
name: string;
Expand Down Expand Up @@ -174,7 +175,7 @@ type CompiledSoftwaresTable = {
Pick<SoftwareExternalData, "externalId" | "label" | "description" | "isLibreSoftware" | "externalDataOrigin">[]
>;
parentWikidataSoftware: JSONColumnType<Pick<SoftwareExternalData, "externalId" | "label" | "description">> | null;
comptoirDuLibreSoftware: JSONColumnType<ComptoirDuLibreSoftware> | null;
comptoirDuLibreSoftware: JSONColumnType<PgComptoirDuLibre.Software> | null;
annuaireCnllServiceProviders: JSONColumnType<
{
name: string;
Expand Down
22 changes: 22 additions & 0 deletions api/src/core/adapters/dbApi/kysely/kysely.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Expression, FunctionModule, RawBuilder, Simplify, sql } from "kysely";

export const jsonBuildObject = <O extends Record<string, Expression<unknown>>>(
obj: O
): RawBuilder<
Simplify<{
[K in keyof O]: O[K] extends Expression<infer V> ? V : never;
}>
> => sql`json_build_object(${sql.join(Object.keys(obj).flatMap(k => [sql.lit(k), obj[k]]))})`;

type NullableToUndefined<A> = A extends null ? Exclude<A, null> | undefined : A;
type StripNullRecursive<T> = {
[K in keyof T]: T[K] extends Record<any, unknown> ? StripNullRecursive<T[K]> : NullableToUndefined<T[K]>;
};
export const jsonStripNulls = <T>(obj: RawBuilder<T>): RawBuilder<StripNullRecursive<T>> =>
sql`json_strip_nulls(${obj})`;

export const jsonAggOrEmptyArray = <Db, E extends Expression<unknown>>(fn: FunctionModule<Db, keyof Db>, value: E) =>
emptyArrayIfNull(fn, fn.jsonAgg(value));

export const emptyArrayIfNull = <Db, E extends Expression<unknown>>(fn: FunctionModule<Db, keyof Db>, value: E) =>
fn.coalesce(value, sql`'[]'`);
5 changes: 5 additions & 0 deletions api/src/core/ports/CompileData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export type CompiledData<T extends "private" | "public"> = CompiledData.Software

export namespace CompiledData {
export type Software<T extends "private" | "public"> = T extends "private" ? Software.Private : Software.Public;

export type SimilarSoftware = Pick<
SoftwareExternalData,
"externalId" | "label" | "description" | "isLibreSoftware" | "externalDataOrigin"
>;
export namespace Software {
export type Common = Pick<
Db.SoftwareRow,
Expand Down
2 changes: 1 addition & 1 deletion api/src/core/usecases/readWriteSillData/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ const softwares = createSelector(compiledData, similarSoftwarePartition, (compil
"versionMin": o.versionMin,
"license": o.license,
"comptoirDuLibreServiceProviderCount": o.comptoirDuLibreSoftware?.providers.length ?? 0,
"annuaireCnllServiceProviders": o.annuaireCnllServiceProviders,
"annuaireCnllServiceProviders": o.annuaireCnllServiceProviders ?? [],
"comptoirDuLibreId": o.comptoirDuLibreSoftware?.id,
"externalId": o.softwareExternalData?.externalId,
"externalDataOrigin": o.softwareExternalData?.externalDataOrigin,
Expand Down
2 changes: 1 addition & 1 deletion api/src/rpc/routes.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe("RPC e2e tests", () => {
const expectedSoftware: Partial<CompiledData.Software<"public">> = {
"description": softwareFormData.softwareDescription,
"externalId": softwareFormData.externalId,
"doRespectRgaa": softwareFormData.doRespectRgaa,
"doRespectRgaa": softwareFormData.doRespectRgaa ?? undefined,
"isFromFrenchPublicService": softwareFormData.isFromFrenchPublicService,
"isPresentInSupportContract": softwareFormData.isPresentInSupportContract,
"keywords": softwareFormData.softwareKeywords,
Expand Down
2 changes: 1 addition & 1 deletion api/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true
},
"include": ["src", "scripts"],
"include": ["src", "scripts"]
}

0 comments on commit 6d63d80

Please sign in to comment.