diff --git a/api/package.json b/api/package.json index 6970a652c..df28b217a 100644 --- a/api/package.json +++ b/api/package.json @@ -26,7 +26,8 @@ "db:seed": "yarn build && dotenv -e ../.env -- node dist/scripts/seed.js", "typecheck": "tsc --noEmit", "migrate": "dotenv -e ../.env -- kysely migrate", - "db:up": "yarn migrate latest", + "migrate:cleanup": "dotenv -e ../.env -- node scripts/cleanup-migrations.js", + "db:up": "yarn migrate:cleanup && yarn migrate latest", "dev:db:up": "docker compose -f ../docker-compose.resources.yml up -d", "dev:db:down": "docker compose -f ../docker-compose.resources.yml down", "dev:db:flush": "rm -rf ../docker-data", diff --git a/api/scripts/cleanup-migrations.js b/api/scripts/cleanup-migrations.js new file mode 100644 index 000000000..3f44c5dda --- /dev/null +++ b/api/scripts/cleanup-migrations.js @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2021-2025 DINUM +// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes +// SPDX-License-Identifier: MIT + +/** + * TEMPORARY SCRIPT: Clean up old migration records before running migrations. + * + * This script removes old migration records from kysely_migration table + * to allow the flattened migration (1760000000000_create-base-tables) to run. + * + * DELETE THIS FILE after the migration has been deployed to production. + */ + +const { Kysely, PostgresDialect, sql } = require("kysely"); +const { Pool } = require("pg"); + +async function cleanupMigrations() { + const pool = new Pool({ + connectionString: process.env.DATABASE_URL + }); + + const db = new Kysely({ + dialect: new PostgresDialect({ pool }) + }); + + try { + // Check if kysely_migration table exists + const { rows } = await sql` + SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_schema = 'public' + AND table_name = 'kysely_migration' + ) as exists + `.execute(db); + + if (!rows[0]?.exists) { + console.log("No kysely_migration table found. Skipping cleanup."); + return; + } + + // Delete all old migration records except the flattened one + const result = await db + .deleteFrom("kysely_migration") + .where("name", "!=", "1760000000000_create-base-tables") + .executeTakeFirst(); + + const numDeleted = Number(result.numDeletedRows || 0); + + if (numDeleted > 0) { + console.log(`✓ Cleaned up ${numDeleted} old migration record(s)`); + } else { + console.log("No old migration records to clean up"); + } + } catch (error) { + console.error("Error cleaning up migrations:", error); + throw error; + } finally { + await db.destroy(); + } +} + +cleanupMigrations().catch(err => { + console.error("Fatal error:", err); + process.exit(1); +}); diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1717162141365_create-initial-tables.ts b/api/src/core/adapters/dbApi/kysely/migrations/1717162141365_create-initial-tables.ts deleted file mode 100644 index 7753bfc22..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1717162141365_create-initial-tables.ts +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import { Kysely, sql } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema.createType("external_data_origin_type").asEnum(["wikidata", "HAL"]).execute(); - await db.schema - .createTable("agents") - .addColumn("id", "serial", col => col.primaryKey()) - .addColumn("email", "text", col => col.notNull()) - .addColumn("organization", "text", col => col.notNull()) - .addColumn("about", "text") - .addColumn("isPublic", "boolean", col => col.notNull()) - .execute(); - - await db.schema - .createTable("softwares") - // from form - .addColumn("id", "serial", col => col.primaryKey()) - .addColumn("softwareType", "jsonb", col => col.notNull()) - .addColumn("externalId", "text") - .addColumn("externalDataOrigin", sql`external_data_origin_type`) - .addColumn("comptoirDuLibreId", "integer") - .addColumn("name", "text", col => col.unique().notNull()) - .addColumn("description", "text", col => col.notNull()) - .addColumn("license", "text", col => col.notNull()) - .addColumn("versionMin", "text", col => col.notNull()) - .addColumn("isPresentInSupportContract", "boolean", col => col.notNull()) - .addColumn("isFromFrenchPublicService", "boolean", col => col.notNull()) - .addColumn("logoUrl", "text") - .addColumn("keywords", "jsonb", col => col.notNull()) - .addColumn("doRespectRgaa", "boolean") - // from ??? - .addColumn("isStillInObservation", "boolean", col => col.notNull()) - .addColumn("parentSoftwareWikidataId", "text") - .addColumn("catalogNumeriqueGouvFrId", "text") - .addColumn("workshopUrls", "jsonb", col => col.notNull()) - .addColumn("testUrls", "jsonb", col => col.notNull()) - .addColumn("categories", "jsonb", col => col.notNull()) - .addColumn("generalInfoMd", "text") - .addColumn("addedByAgentId", "integer", col => col.notNull().references("agents.id")) - .addColumn("dereferencing", "jsonb") - .addColumn("referencedSinceTime", "bigint", col => col.notNull()) - .addColumn("updateTime", "bigint", col => col.notNull()) - .addColumn("lastExtraDataFetchAt", "timestamptz") - .execute(); - - await db.schema - .createTable("software_external_datas") - .addColumn("externalId", "text", col => col.primaryKey()) - .addColumn("externalDataOrigin", sql`external_data_origin_type`, col => col.notNull()) - .addColumn("developers", "jsonb", col => col.notNull()) - .addColumn("label", "jsonb", col => col.notNull()) - .addColumn("description", "jsonb", col => col.notNull()) - .addColumn("isLibreSoftware", "boolean", col => col.notNull()) - .addColumn("logoUrl", "text") - .addColumn("framaLibreId", "text") - .addColumn("websiteUrl", "text") - .addColumn("sourceUrl", "text") - .addColumn("documentationUrl", "text") - .addColumn("license", "text") - .execute(); - - await db.schema - .createTable("softwares__similar_software_external_datas") - .addColumn("softwareId", "integer", col => col.notNull().references("softwares.id").onDelete("cascade")) - .addColumn("similarExternalId", "text", col => col.notNull()) - .addUniqueConstraint("uniq_software_id__similar_software_external_id", ["softwareId", "similarExternalId"]) - .execute(); - - await db.schema - .createTable("software_users") - .addColumn("softwareId", "integer", col => col.notNull().references("softwares.id").onDelete("cascade")) - .addColumn("agentId", "integer", col => col.notNull().references("agents.id").onDelete("cascade")) - .addColumn("useCaseDescription", "text", col => col.notNull()) - .addColumn("os", "text") - .addColumn("version", "text", col => col.notNull()) - .addColumn("serviceUrl", "text") - .execute(); - - await db.schema - .createTable("software_referents") - .addColumn("softwareId", "integer", col => col.notNull().references("softwares.id").onDelete("cascade")) - .addColumn("agentId", "integer", col => col.notNull().references("agents.id").onDelete("cascade")) - .addColumn("useCaseDescription", "text", col => col.notNull()) - .addColumn("isExpert", "boolean", col => col.notNull()) - .addColumn("serviceUrl", "text") - .execute(); - - await db.schema - .createTable("instances") - .addColumn("id", "serial", col => col.primaryKey()) - .addColumn("mainSoftwareSillId", "integer", col => col.notNull().references("softwares.id").onDelete("cascade")) - .addColumn("addedByAgentId", "integer", col => col.notNull().references("agents.id")) - .addColumn("organization", "text", col => col.notNull()) - .addColumn("targetAudience", "text", col => col.notNull()) - .addColumn("instanceUrl", "text") - .addColumn("isPublic", "boolean", col => col.notNull()) - .addColumn("referencedSinceTime", "bigint", col => col.notNull()) - .addColumn("updateTime", "bigint", col => col.notNull()) - .execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.dropTable("softwares__similar_software_external_datas").execute(); - await db.schema.dropTable("software_external_datas").execute(); - await db.schema.dropTable("instances").execute(); - await db.schema.dropTable("software_referents").execute(); - await db.schema.dropTable("software_users").execute(); - await db.schema.dropTable("softwares").execute(); - await db.schema.dropTable("agents").execute(); - await db.schema.dropType("external_data_origin_type").execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1719576701920_add-compiled-tables.ts b/api/src/core/adapters/dbApi/kysely/migrations/1719576701920_add-compiled-tables.ts deleted file mode 100644 index 96bf7f3df..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1719576701920_add-compiled-tables.ts +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import type { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema - .createTable("compiled_softwares") - .addColumn("softwareId", "integer", col => - col.notNull().primaryKey().references("softwares.id").onDelete("cascade") - ) - .addColumn("serviceProviders", "jsonb", col => col.notNull()) - .addColumn("comptoirDuLibreSoftware", "jsonb") - .addColumn("annuaireCnllServiceProviders", "jsonb") - .addColumn("latestVersion", "jsonb") - .execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.dropTable("compiled_softwares").execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1720187269028_add-indexes.ts b/api/src/core/adapters/dbApi/kysely/migrations/1720187269028_add-indexes.ts deleted file mode 100644 index 9d0f99397..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1720187269028_add-indexes.ts +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import type { Kysely } from "kysely"; - -const softwares_externalIdIdx = "softwares__externalId_idx"; -const softwares_parentExternalIdIdx = "softwares__parentExternalId_idx"; -const compiledSoftwares_SoftwareIdIdx = "compiled_softwares__softwareId_idx"; -const software_similarExternalIdIdx = "softwares_similarExternalId_idx"; -const software_softwareIdIdx = "softwares_similarSoftwareId_idx"; -const softwareReferents_softwareIdIdx = "softwareReferents_software_idx"; -const softwareUsers_softwareIdIdx = "softwareUsers_software_idx"; -const instances_mainSoftwareSillIdIdx = "instances_mainSoftwareSillId_idx"; - -export async function up(db: Kysely): Promise { - await db.schema.createIndex(softwares_externalIdIdx).on("softwares").column("externalId").execute(); - await db.schema - .createIndex(softwares_parentExternalIdIdx) - .on("softwares") - .column("parentSoftwareWikidataId") - .execute(); - - await db.schema - .createIndex(software_similarExternalIdIdx) - .on("softwares__similar_software_external_datas") - .column("similarExternalId") - .execute(); - await db.schema - .createIndex(software_softwareIdIdx) - .on("softwares__similar_software_external_datas") - .column("softwareId") - .execute(); - - await db.schema - .createIndex(compiledSoftwares_SoftwareIdIdx) - .on("compiled_softwares") - .column("softwareId") - .execute(); - await db.schema - .createIndex(softwareReferents_softwareIdIdx) - .on("software_referents") - .column("softwareId") - .execute(); - await db.schema.createIndex(softwareUsers_softwareIdIdx).on("software_users").column("softwareId").execute(); - await db.schema.createIndex(instances_mainSoftwareSillIdIdx).on("instances").column("mainSoftwareSillId").execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.dropIndex(softwares_parentExternalIdIdx).execute(); - await db.schema.dropIndex(softwares_externalIdIdx).execute(); - await db.schema.dropIndex(software_similarExternalIdIdx).execute(); - await db.schema.dropIndex(software_softwareIdIdx).execute(); - await db.schema.dropIndex(compiledSoftwares_SoftwareIdIdx).execute(); - await db.schema.dropIndex(softwareReferents_softwareIdIdx).execute(); - await db.schema.dropIndex(softwareUsers_softwareIdIdx).execute(); - await db.schema.dropIndex(instances_mainSoftwareSillIdIdx).execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1731574107668_make_version_min_nullable_in_softwares_and_add_columns_on_software_external_datas.ts b/api/src/core/adapters/dbApi/kysely/migrations/1731574107668_make_version_min_nullable_in_softwares_and_add_columns_on_software_external_datas.ts deleted file mode 100644 index 8eb1b9512..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1731574107668_make_version_min_nullable_in_softwares_and_add_columns_on_software_external_datas.ts +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema - .alterTable("softwares") - .alterColumn("versionMin", ac => ac.dropNotNull()) - .execute(); - - await db.schema - .alterTable("software_external_datas") - .addColumn("softwareVersion", "text") - .addColumn("publicationTime", "timestamptz") - .addColumn("keywords", "jsonb") - .addColumn("programmingLanguages", "jsonb") - .addColumn("applicationCategories", "jsonb") - .execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema - .alterTable("softwares") - .alterColumn("versionMin", ac => ac.setNotNull()) - .execute(); - - await db.schema - .alterTable("software_external_datas") - .dropColumn("softwareVersion") - .dropColumn("publicationTime") - .dropColumn("keywords") - .dropColumn("programmingLanguages") - .dropColumn("applicationCategories") - .execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1733923587668_make-organization-nullable-for-agent.ts b/api/src/core/adapters/dbApi/kysely/migrations/1733923587668_make-organization-nullable-for-agent.ts deleted file mode 100644 index 182d2ebb3..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1733923587668_make-organization-nullable-for-agent.ts +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import type { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema - .alterTable("agents") - .alterColumn("organization", ac => ac.dropNotNull()) - .execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema - .alterTable("agents") - .alterColumn("organization", ac => ac.setNotNull()) - .execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1737023616811_add-referencePublication.ts b/api/src/core/adapters/dbApi/kysely/migrations/1737023616811_add-referencePublication.ts deleted file mode 100644 index 3d8e245ee..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1737023616811_add-referencePublication.ts +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema.alterTable("software_external_datas").addColumn("referencePublications", "jsonb").execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.alterTable("software_external_datas").dropColumn("referencePublications").execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1738152406578_add-identifiers.ts b/api/src/core/adapters/dbApi/kysely/migrations/1738152406578_add-identifiers.ts deleted file mode 100644 index fde81ca33..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1738152406578_add-identifiers.ts +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema.alterTable("software_external_datas").addColumn("identifiers", "jsonb").execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.alterTable("software_external_datas").dropColumn("identifiers").execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1740045549729_move-framalibreid.ts b/api/src/core/adapters/dbApi/kysely/migrations/1740045549729_move-framalibreid.ts deleted file mode 100644 index b45c50b3e..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1740045549729_move-framalibreid.ts +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - // Merging data - // The data on software_external_datas will update themselves on the next update - - await db.schema.alterTable("software_external_datas").dropColumn("framaLibreId").execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.alterTable("software_external_datas").addColumn("framaLibreId", "text").execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1740125749274_remove-depreciated-fields.ts b/api/src/core/adapters/dbApi/kysely/migrations/1740125749274_remove-depreciated-fields.ts deleted file mode 100644 index cf781723a..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1740125749274_remove-depreciated-fields.ts +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema.alterTable("softwares").dropColumn("catalogNumeriqueGouvFrId").dropColumn("testUrls").execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema - .alterTable("softwares") - .addColumn("testUrls", "jsonb") - .addColumn("catalogNumeriqueGouvFrId", "text") - .execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1742990557548_remove-parent-software-related-columns.ts b/api/src/core/adapters/dbApi/kysely/migrations/1742990557548_remove-parent-software-related-columns.ts deleted file mode 100644 index 9ca01b79b..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1742990557548_remove-parent-software-related-columns.ts +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import type { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema.dropIndex("softwares__parentExternalId_idx").execute(); - await db.schema.alterTable("softwares").dropColumn("parentSoftwareWikidataId").execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.alterTable("softwares").addColumn("parentSoftwareWikidataId", "text").execute(); - // we don't bother with the index -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1742990558000_add-source-table.ts b/api/src/core/adapters/dbApi/kysely/migrations/1742990558000_add-source-table.ts deleted file mode 100644 index 623e16574..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1742990558000_add-source-table.ts +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import { sql, type Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema - .createTable("sources") - .addColumn("slug", "text", col => col.primaryKey()) - .addColumn("kind", sql`external_data_origin_type`, col => col.notNull()) - .addColumn("url", "text", col => col.notNull()) - .addColumn("priority", "integer", col => col.unique().notNull()) - .addColumn("description", "jsonb") - .execute(); - - await db.schema - .alterTable("software_external_datas") - .addColumn("sourceSlug", "text", col => col.references("sources.slug")) - .addColumn("softwareId", "integer", col => col.references("softwares.id")) - .execute(); - - // Insert the source used until now in the table - const firstExistingSoftware = await db - .selectFrom("softwares") - .select("externalDataOrigin") - .limit(1) - .executeTakeFirst(); - - const mainSource = - firstExistingSoftware?.externalDataOrigin === "HAL" - ? { - slug: "hal", - kind: "HAL", - url: "https://hal.science", - priority: 1 - } - : { - slug: "wikidata", - kind: "wikidata", - url: "https://www.wikidata.org", - priority: 1 - }; - - await db.insertInto("sources").values(mainSource).executeTakeFirst(); - await db.updateTable("software_external_datas").set({ sourceSlug: mainSource.slug }).execute(); - - await db.schema - .alterTable("softwares__similar_software_external_datas") - .addColumn("sourceSlug", "text", col => col.references("sources.slug")) - .execute(); - await db.updateTable("softwares__similar_software_external_datas").set({ sourceSlug: mainSource.slug }).execute(); - await db.schema - .alterTable("softwares__similar_software_external_datas") - .alterColumn("sourceSlug", col => col.setNotNull()) - .execute(); - - // add unique contraint on sourceSlug + externalId + softwareId - await db.schema - .alterTable("software_external_datas") - .addUniqueConstraint("uniq_source_external_id", ["sourceSlug", "externalId", "softwareId"]) - .execute(); - - await db - .updateTable("software_external_datas") - .set(eb => ({ - softwareId: eb - .selectFrom("softwares") - .select("id") - .whereRef("softwares.externalId", "=", "software_external_datas.externalId") - .limit(1) - })) - .execute(); - - await db.schema - .alterTable("software_external_datas") - .alterColumn("sourceSlug", col => col.setNotNull()) - .dropColumn("externalDataOrigin") - .execute(); - - await db.schema - .alterTable("softwares") - .addColumn("sourceSlug", "text", col => col.references("sources.slug")) - .execute(); - - await db - .updateTable("softwares") - .set({ sourceSlug: mainSource.slug }) - .where("externalDataOrigin", "=", mainSource.kind) - .execute(); - - await db.schema.alterTable("softwares").dropColumn("externalDataOrigin").execute(); - - await db.schema.alterTable("softwares").renameColumn("externalId", "externalIdForSource").execute(); -} - -export async function down(db: Kysely): Promise { - const sources = await db.selectFrom("sources").selectAll().execute(); - - await db.schema.alterTable("software_external_datas").dropConstraint("uniq_source_external_id").execute(); - - await db.schema.alterTable("software_external_datas").addColumn("externalDataOrigin", "text").execute(); - - for (const source of sources) { - await db - .updateTable("software_external_datas") - .set({ externalDataOrigin: source.kind }) - .where("sourceSlug", "=", source.slug) - .execute(); - } - - await db.schema.alterTable("software_external_datas").dropColumn("sourceSlug").dropColumn("softwareId").execute(); - - await db.schema.alterTable("softwares").addColumn("externalDataOrigin", "text").execute(); - - for (const source of sources) { - await db - .updateTable("softwares") - .set({ externalDataOrigin: source.kind }) - .where("sourceSlug", "=", source.slug) - .execute(); - } - - await db.schema.alterTable("softwares").renameColumn("externalIdForSource", "externalId").execute(); - - await db.schema.alterTable("softwares").dropColumn("sourceSlug").execute(); - - await db.schema.alterTable("softwares__similar_software_external_datas").dropColumn("sourceSlug").execute(); - - await db.schema.dropTable("sources").execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1744719952208-update_external_software.ts b/api/src/core/adapters/dbApi/kysely/migrations/1744719952208-update_external_software.ts deleted file mode 100644 index 18cc286d4..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1744719952208-update_external_software.ts +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import type { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema - .alterTable("software_external_datas") - .alterColumn("isLibreSoftware", col => col.dropNotNull()) - .addColumn("lastDataFetchAt", "bigint") - .execute(); - - await db.schema.alterTable("softwares").dropColumn("lastExtraDataFetchAt").execute(); - - // Change PK on software_external_datas - await db.schema.alterTable("software_external_datas").dropConstraint("software_external_datas_pkey").execute(); - - await db.schema - .alterTable("software_external_datas") - .addPrimaryKeyConstraint("software_external_datas_pkey", ["externalId", "sourceSlug"]) - .execute(); - - // Add on delete cascade - await db.schema - .alterTable("software_external_datas") - .dropConstraint("software_external_datas_softwareId_fkey") - .execute(); - await db.schema - .alterTable("software_external_datas") - .dropConstraint("software_external_datas_sourceSlug_fkey") - .execute(); - - await db.schema - .alterTable("software_external_datas") - .addForeignKeyConstraint( - "software_external_datas_softwareId_fkey", - ["softwareId"], - "softwares", - ["id"], - constraint => constraint.onDelete("cascade") - ) - .execute(); - - await db.schema - .alterTable("software_external_datas") - .addForeignKeyConstraint( - "software_external_datas_sourceSlug_fkey", - ["sourceSlug"], - "sources", - ["slug"], - constraint => constraint.onDelete("cascade") - ) - .execute(); - - // Add on delete cascade between similarSoftware and externalData - await db.schema - .alterTable("softwares__similar_software_external_datas") - .dropConstraint("softwares__similar_software_external_datas_sourceSlug_fkey") - .execute(); - - await db.schema - .alterTable("softwares__similar_software_external_datas") - .addForeignKeyConstraint( - "softwares__similar_software_external_datas_software_external_datas_fkey", - ["similarExternalId", "sourceSlug"], - "software_external_datas", - ["externalId", "sourceSlug"], - constraint => constraint.onDelete("cascade") - ) - .execute(); - - // Change PK on softwares__similar_software_external_datas - await db.schema - .alterTable("softwares__similar_software_external_datas") - .dropConstraint("uniq_software_id__similar_software_external_id") - .execute(); - - await db.schema - .alterTable("softwares__similar_software_external_datas") - .addPrimaryKeyConstraint("softwares__similar_software_external_datas_pkey", [ - "softwareId", - "similarExternalId", - "sourceSlug" - ]) - .execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema - .alterTable("software_external_datas") - .alterColumn("isLibreSoftware", col => col.setNotNull()) - .dropColumn("lastDataFetchAt") - .execute(); - - await db.schema.alterTable("softwares").addColumn("lastExtraDataFetchAt", "timestamptz").execute(); - - await db.schema.alterTable("software_external_datas").dropConstraint("software_external_datas_pkey").execute(); - - await db.schema - .alterTable("software_external_datas") - .addPrimaryKeyConstraint("software_external_datas_pkey", ["externalId"]) - .execute(); - - // Remove on delete cascade - await db.schema - .alterTable("software_external_datas") - .dropConstraint("software_external_datas_softwareId_fkey") - .execute(); - await db.schema - .alterTable("software_external_datas") - .dropConstraint("software_external_datas_sourceSlug_fkey") - .execute(); - - await db.schema - .alterTable("software_external_datas") - .addForeignKeyConstraint("software_external_datas_softwareId_fkey", ["softwareId"], "softwares", ["id"]) - .execute(); - - await db.schema - .alterTable("software_external_datas") - .addForeignKeyConstraint("software_external_datas_sourceSlug_fkey", ["sourceSlug"], "sources", ["slug"]) - .execute(); - - // Revert to a single forein key without cascade - await db.schema - .alterTable("softwares__similar_software_external_datas") - .dropConstraint("softwares__similar_software_external_datas_software_external_datas_fkey") - .execute(); - - await db.schema - .alterTable("softwares__similar_software_external_datas") - .addForeignKeyConstraint( - "softwares__similar_software_external_datas_sourceSlug_fkey", - ["sourceSlug"], - "sources", - ["slug"] - ) - .execute(); - - // Revet PK to unique on softwares__similar_software_external_datas - await db.schema - .alterTable("softwares__similar_software_external_datas") - .dropConstraint("softwares__similar_software_external_datas_pkey") - .execute(); - - await db.schema - .alterTable("softwares__similar_software_external_datas") - .addPrimaryKeyConstraint("uniq_software_id__similar_software_external_id", ["softwareId", "similarExternalId"]) - .execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1745395147722_move-prodivers.ts b/api/src/core/adapters/dbApi/kysely/migrations/1745395147722_move-prodivers.ts deleted file mode 100644 index f8eb2b36e..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1745395147722_move-prodivers.ts +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import type { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema.alterTable("software_external_datas").addColumn("providers", "jsonb").execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.alterTable("software_external_datas").dropColumn("providers").execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1745402275178-add_new_source_kind.ts b/api/src/core/adapters/dbApi/kysely/migrations/1745402275178-add_new_source_kind.ts deleted file mode 100644 index 9d8b2a3ad..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1745402275178-add_new_source_kind.ts +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import { sql, type Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema - .alterTable("sources") - .alterColumn("kind", col => col.setDataType("text")) - .execute(); - - await db.schema.dropType("external_data_origin_type").execute(); - await db.schema.createType("external_data_origin_type").asEnum(["wikidata", "HAL", "ComptoirDuLibre"]).execute(); - - await db.schema - .alterTable("sources") - .alterColumn("kind", col => - col.setDataType(sql`external_data_origin_type USING kind::external_data_origin_type`) - ) - .execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema - .alterTable("sources") - .alterColumn("kind", col => col.setDataType("text")) - .execute(); - - await db.schema.dropType("external_data_origin_type").execute(); - await db.schema.createType("external_data_origin_type").asEnum(["wikidata", "HAL"]).execute(); - - await db.schema - .alterTable("sources") - .alterColumn("kind", col => - col.setDataType(sql`external_data_origin_type USING kind::external_data_origin_type`) - ) - .execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1747296861930-add_source_CNLL.ts b/api/src/core/adapters/dbApi/kysely/migrations/1747296861930-add_source_CNLL.ts deleted file mode 100644 index 6d482c244..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1747296861930-add_source_CNLL.ts +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import { sql, type Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema - .alterTable("sources") - .alterColumn("kind", col => col.setDataType("text")) - .execute(); - - await db.schema.dropType("external_data_origin_type").execute(); - await db.schema - .createType("external_data_origin_type") - .asEnum(["wikidata", "HAL", "ComptoirDuLibre", "CNLL"]) - .execute(); - - await db.schema - .alterTable("sources") - .alterColumn("kind", col => - col.setDataType(sql`external_data_origin_type USING kind::external_data_origin_type`) - ) - .execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema - .alterTable("sources") - .alterColumn("kind", col => col.setDataType("text")) - .execute(); - - await db.schema.dropType("external_data_origin_type").execute(); - await db.schema.createType("external_data_origin_type").asEnum(["wikidata", "HAL", "ComptoirDuLibre"]).execute(); - - await db.schema - .alterTable("sources") - .alterColumn("kind", col => - col.setDataType(sql`external_data_origin_type USING kind::external_data_origin_type`) - ) - .execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1747311447673-add_zenodo_gateway.ts b/api/src/core/adapters/dbApi/kysely/migrations/1747311447673-add_zenodo_gateway.ts deleted file mode 100644 index e13091f3d..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1747311447673-add_zenodo_gateway.ts +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import { sql, type Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema - .alterTable("sources") - .alterColumn("kind", col => col.setDataType("text")) - .execute(); - - await db.schema.dropType("external_data_origin_type").execute(); - await db.schema - .createType("external_data_origin_type") - .asEnum(["wikidata", "HAL", "ComptoirDuLibre", "CNLL", "Zenodo"]) - .execute(); - - await db.schema - .alterTable("sources") - .alterColumn("kind", col => - col.setDataType(sql`external_data_origin_type USING kind::external_data_origin_type`) - ) - .execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema - .alterTable("sources") - .alterColumn("kind", col => col.setDataType("text")) - .execute(); - - await db.schema.dropType("external_data_origin_type").execute(); - await db.schema - .createType("external_data_origin_type") - .asEnum(["wikidata", "HAL", "ComptoirDuLibre", "CNLL"]) - .execute(); - - await db.schema - .alterTable("sources") - .alterColumn("kind", col => - col.setDataType(sql`external_data_origin_type USING kind::external_data_origin_type`) - ) - .execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1751033845081_move-comptoire-du-libre-data-to-external-data.ts b/api/src/core/adapters/dbApi/kysely/migrations/1751033845081_move-comptoire-du-libre-data-to-external-data.ts deleted file mode 100644 index 928eb5f34..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1751033845081_move-comptoire-du-libre-data-to-external-data.ts +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import type { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - const comptoirDuLibreSourceSlug = "comptoir-du-libre"; - await db - .insertInto("sources") - .values({ - kind: "ComptoirDuLibre", - slug: comptoirDuLibreSourceSlug, - description: JSON.stringify({ fr: "Comptoir du Libre" }), - url: "https://comptoir-du-libre.org", - priority: 2 - }) - .onConflict(oc => oc.column("slug").doNothing()) - .execute(); - - const softsWithSourceComptoireDuLibre = await db - .selectFrom("softwares") - .select(["id", "comptoirDuLibreId", "name", "description"]) - .where("comptoirDuLibreId", "is not", null) - .where("dereferencing", "is", null) - .execute(); - - if (softsWithSourceComptoireDuLibre.length > 0) { - await db - .insertInto("software_external_datas") - .values( - softsWithSourceComptoireDuLibre.map(s => ({ - softwareId: s.id, - externalId: s.comptoirDuLibreId.toString(), - sourceSlug: comptoirDuLibreSourceSlug, - label: JSON.stringify(s.name), - description: JSON.stringify(s.description), - developers: JSON.stringify([]) - })) - ) - .execute(); - } - - await db.schema.alterTable("softwares").dropColumn("comptoirDuLibreId").execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.alterTable("softwares").addColumn("comptoirDuLibreId", "integer").execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1751616349981_rename-agents-table-to-users.ts b/api/src/core/adapters/dbApi/kysely/migrations/1751616349981_rename-agents-table-to-users.ts deleted file mode 100644 index b17160352..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1751616349981_rename-agents-table-to-users.ts +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import type { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema.alterTable("agents").renameTo("users").execute(); - - await db.schema.alterTable("software_users").renameColumn("agentId", "userId").execute(); - await db.schema.alterTable("software_referents").renameColumn("agentId", "userId").execute(); - await db.schema.alterTable("softwares").renameColumn("addedByAgentId", "addedByUserId").execute(); - await db.schema.alterTable("instances").renameColumn("addedByAgentId", "addedByUserId").execute(); - - await db.schema - .alterTable("users") - .addColumn("createdAt", "timestamptz") - .addColumn("updatedAt", "timestamptz") - .execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.alterTable("users").dropColumn("createdAt").dropColumn("updatedAt").execute(); - - await db.schema.alterTable("softwares").renameColumn("addedByUserId", "addedByAgentId").execute(); - await db.schema.alterTable("instances").renameColumn("addedByUserId", "addedByAgentId").execute(); - await db.schema.alterTable("software_users").renameColumn("userId", "agentId").execute(); - await db.schema.alterTable("software_referents").renameColumn("userId", "agentId").execute(); - - await db.schema.alterTable("users").renameTo("agents").execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1751616349982_create-sessions-table.ts b/api/src/core/adapters/dbApi/kysely/migrations/1751616349982_create-sessions-table.ts deleted file mode 100644 index bb6f14fbd..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1751616349982_create-sessions-table.ts +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import type { Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema - .createTable("user_sessions") - .addColumn("id", "uuid", col => col.primaryKey()) - .addColumn("state", "text", col => col.notNull()) - .addColumn("redirectUrl", "text") - .addColumn("userId", "integer", col => col.references("users.id").onDelete("cascade")) - .addColumn("email", "text") - .addColumn("accessToken", "text") - .addColumn("refreshToken", "text") - .addColumn("idToken", "text") - .addColumn("expiresAt", "timestamptz") - .addColumn("createdAt", "timestamptz", col => col.notNull().defaultTo("now()")) - .addColumn("updatedAt", "timestamptz", col => col.notNull().defaultTo("now()")) - .addColumn("loggedOutAt", "timestamptz") - .execute(); - - await db.schema.createIndex("sessions_state_idx").on("user_sessions").column("state").execute(); - - await db.schema.createIndex("sessions_userId_idx").on("user_sessions").column("userId").execute(); - - await db.schema.createIndex("sessions_expiresAt_idx").on("user_sessions").column("expiresAt").execute(); - - await db.schema - .alterTable("users") - .addColumn("sub", "text") - .addColumn("firstName", "text") - .addColumn("lastName", "text") - .execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.alterTable("users").dropColumn("sub").dropColumn("firstName").dropColumn("lastName").execute(); - - await db.schema.dropTable("user_sessions").execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1752223948181_delete-old-data.ts b/api/src/core/adapters/dbApi/kysely/migrations/1752223948181_delete-old-data.ts deleted file mode 100644 index 97473bef0..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1752223948181_delete-old-data.ts +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import { type Kysely } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema.dropTable("compiled_softwares").execute(); - - // Update with job:update after to load comptoirDuLibre data - await db - .insertInto("sources") - .values({ - slug: "cnll", - kind: "CNLL", - url: "https://cnll.fr/", - priority: 3, - description: JSON.stringify({ fr: "Union des entreprises du logiciel libre et du numérique ouvert" }) - }) - .onConflict(oc => oc.column("slug").doNothing()) - .execute(); - - // Ensure the trace of bind between tables - const idsToWrite = await db - .selectFrom("softwares as s") - .leftJoin("software_external_datas as ext", "ext.externalId", "s.externalIdForSource") - .select(["s.id", "s.sourceSlug", "s.externalIdForSource"]) - // Only select if you have data to insert - .where("s.externalIdForSource", "is not", null) - // When join is not already made - .whereRef("ext.softwareId", "!=", "s.id") - .where("s.dereferencing", "is", null) - .execute(); - - if (idsToWrite?.length > 0) { - await Promise.all( - idsToWrite.map(({ id, externalIdForSource, sourceSlug }) => { - db.updateTable("software_external_datas") - .set({ - sourceSlug, - externalId: externalIdForSource - }) - .where("softwareId", "=", id) - .executeTakeFirst(); - }) - ); - } - - // Delete - await db.schema.alterTable("softwares").dropColumn("externalIdForSource").dropColumn("sourceSlug").execute(); -} - -export async function down(db: Kysely): Promise { - const compiledSoftwares_SoftwareIdIdx = "compiled_softwares__softwareId_idx"; - - await db.schema - .createTable("compiled_softwares") - .addColumn("softwareId", "integer", col => - col.notNull().primaryKey().references("softwares.id").onDelete("cascade") - ) - .addColumn("serviceProviders", "jsonb", col => col.notNull()) - .addColumn("comptoirDuLibreSoftware", "jsonb") - .addColumn("annuaireCnllServiceProviders", "jsonb") - .addColumn("latestVersion", "jsonb") - .execute(); - - await db.schema - .createIndex(compiledSoftwares_SoftwareIdIdx) - .on("compiled_softwares") - .column("softwareId") - .execute(); - - await db.schema - .alterTable("softwares") - .addColumn("sourceSlug", "text", col => col.references("sources.slug")) - .addColumn("externalIdForSource", "text") - .execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1759498443784_make-timestamps-columns-human-readable.ts b/api/src/core/adapters/dbApi/kysely/migrations/1759498443784_make-timestamps-columns-human-readable.ts deleted file mode 100644 index fd9acff45..000000000 --- a/api/src/core/adapters/dbApi/kysely/migrations/1759498443784_make-timestamps-columns-human-readable.ts +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2025 DINUM -// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes -// SPDX-License-Identifier: MIT - -import { Kysely, sql } from "kysely"; - -export async function up(db: Kysely): Promise { - await db.schema.alterTable("softwares").addColumn("referencedSinceTime_temp", "timestamptz").execute(); - await sql`UPDATE softwares SET "referencedSinceTime_temp" = to_timestamp("referencedSinceTime" / 1000.0)`.execute( - db - ); - await db.schema - .alterTable("softwares") - .alterColumn("referencedSinceTime_temp", col => col.setNotNull()) - .execute(); - await db.schema.alterTable("softwares").dropColumn("referencedSinceTime").execute(); - await db.schema.alterTable("softwares").renameColumn("referencedSinceTime_temp", "referencedSinceTime").execute(); - - await db.schema.alterTable("softwares").addColumn("updateTime_temp", "timestamptz").execute(); - await sql`UPDATE softwares SET "updateTime_temp" = to_timestamp("updateTime" / 1000.0)`.execute(db); - await db.schema - .alterTable("softwares") - .alterColumn("updateTime_temp", col => col.setNotNull()) - .execute(); - await db.schema.alterTable("softwares").dropColumn("updateTime").execute(); - await db.schema.alterTable("softwares").renameColumn("updateTime_temp", "updateTime").execute(); - - await db.schema.alterTable("instances").addColumn("referencedSinceTime_temp", "timestamptz").execute(); - await sql`UPDATE instances SET "referencedSinceTime_temp" = to_timestamp("referencedSinceTime" / 1000.0)`.execute( - db - ); - await db.schema - .alterTable("instances") - .alterColumn("referencedSinceTime_temp", col => col.setNotNull()) - .execute(); - await db.schema.alterTable("instances").dropColumn("referencedSinceTime").execute(); - await db.schema.alterTable("instances").renameColumn("referencedSinceTime_temp", "referencedSinceTime").execute(); - - await db.schema.alterTable("instances").addColumn("updateTime_temp", "timestamptz").execute(); - await sql`UPDATE instances SET "updateTime_temp" = to_timestamp("updateTime" / 1000.0)`.execute(db); - await db.schema - .alterTable("instances") - .alterColumn("updateTime_temp", col => col.setNotNull()) - .execute(); - await db.schema.alterTable("instances").dropColumn("updateTime").execute(); - await db.schema.alterTable("instances").renameColumn("updateTime_temp", "updateTime").execute(); - - await db.schema.alterTable("software_external_datas").addColumn("lastDataFetchAt_temp", "timestamptz").execute(); - await sql`UPDATE software_external_datas SET "lastDataFetchAt_temp" = to_timestamp("lastDataFetchAt" / 1000.0) WHERE "lastDataFetchAt" IS NOT NULL`.execute( - db - ); - await db.schema.alterTable("software_external_datas").dropColumn("lastDataFetchAt").execute(); - await db.schema - .alterTable("software_external_datas") - .renameColumn("lastDataFetchAt_temp", "lastDataFetchAt") - .execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.alterTable("softwares").addColumn("referencedSinceTime_temp", "bigint").execute(); - await sql`UPDATE softwares SET "referencedSinceTime_temp" = EXTRACT(EPOCH FROM "referencedSinceTime")::bigint * 1000`.execute( - db - ); - await db.schema - .alterTable("softwares") - .alterColumn("referencedSinceTime_temp", col => col.setNotNull()) - .execute(); - await db.schema.alterTable("softwares").dropColumn("referencedSinceTime").execute(); - await db.schema.alterTable("softwares").renameColumn("referencedSinceTime_temp", "referencedSinceTime").execute(); - - await db.schema.alterTable("softwares").addColumn("updateTime_temp", "bigint").execute(); - await sql`UPDATE softwares SET "updateTime_temp" = EXTRACT(EPOCH FROM "updateTime")::bigint * 1000`.execute(db); - await db.schema - .alterTable("softwares") - .alterColumn("updateTime_temp", col => col.setNotNull()) - .execute(); - await db.schema.alterTable("softwares").dropColumn("updateTime").execute(); - await db.schema.alterTable("softwares").renameColumn("updateTime_temp", "updateTime").execute(); - - await db.schema.alterTable("instances").addColumn("referencedSinceTime_temp", "bigint").execute(); - await sql`UPDATE instances SET "referencedSinceTime_temp" = EXTRACT(EPOCH FROM "referencedSinceTime")::bigint * 1000`.execute( - db - ); - await db.schema - .alterTable("instances") - .alterColumn("referencedSinceTime_temp", col => col.setNotNull()) - .execute(); - await db.schema.alterTable("instances").dropColumn("referencedSinceTime").execute(); - await db.schema.alterTable("instances").renameColumn("referencedSinceTime_temp", "referencedSinceTime").execute(); - - await db.schema.alterTable("instances").addColumn("updateTime_temp", "bigint").execute(); - await sql`UPDATE instances SET "updateTime_temp" = EXTRACT(EPOCH FROM "updateTime")::bigint * 1000`.execute(db); - await db.schema - .alterTable("instances") - .alterColumn("updateTime_temp", col => col.setNotNull()) - .execute(); - await db.schema.alterTable("instances").dropColumn("updateTime").execute(); - await db.schema.alterTable("instances").renameColumn("updateTime_temp", "updateTime").execute(); - - await db.schema.alterTable("software_external_datas").addColumn("lastDataFetchAt_temp", "bigint").execute(); - await sql`UPDATE software_external_datas SET "lastDataFetchAt_temp" = EXTRACT(EPOCH FROM "lastDataFetchAt")::bigint * 1000 WHERE "lastDataFetchAt" IS NOT NULL`.execute( - db - ); - await db.schema.alterTable("software_external_datas").dropColumn("lastDataFetchAt").execute(); - await db.schema - .alterTable("software_external_datas") - .renameColumn("lastDataFetchAt_temp", "lastDataFetchAt") - .execute(); -} diff --git a/api/src/core/adapters/dbApi/kysely/migrations/1759500000000_create-base-tables.ts b/api/src/core/adapters/dbApi/kysely/migrations/1759500000000_create-base-tables.ts new file mode 100644 index 000000000..227f1c74c --- /dev/null +++ b/api/src/core/adapters/dbApi/kysely/migrations/1759500000000_create-base-tables.ts @@ -0,0 +1,227 @@ +// SPDX-FileCopyrightText: 2021-2025 DINUM +// SPDX-FileCopyrightText: 2024-2025 Université Grenoble Alpes +// SPDX-License-Identifier: MIT + +import { Kysely, sql } from "kysely"; + +export async function up(db: Kysely): Promise { + // Check if this is an existing database by looking for the user_sessions table + const { rows } = await sql<{ exists: boolean }>` + SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_schema = 'public' + AND table_name = 'user_sessions' + ) as exists + `.execute(db); + + const isExistingDatabase = rows[0]?.exists; + + if (isExistingDatabase) { + // For existing databases, schema already exists, nothing to do + console.log("Existing database detected. Schema already exists, skipping creation."); + return; + } + + // For new databases, create the complete schema + console.log("New database detected. Creating complete schema..."); + + // Create enum type + await db.schema + .createType("external_data_origin_type") + .asEnum(["wikidata", "HAL", "ComptoirDuLibre", "CNLL", "Zenodo"]) + .execute(); + + // Create users table (formerly agents) + await db.schema + .createTable("users") + .addColumn("id", "serial", col => col.primaryKey()) + .addColumn("email", "text", col => col.notNull()) + .addColumn("organization", "text") + .addColumn("about", "text") + .addColumn("isPublic", "boolean", col => col.notNull()) + .addColumn("createdAt", "timestamptz") + .addColumn("updatedAt", "timestamptz") + .addColumn("sub", "text") + .addColumn("firstName", "text") + .addColumn("lastName", "text") + .execute(); + + // Create softwares table + await db.schema + .createTable("softwares") + .addColumn("id", "serial", col => col.primaryKey()) + .addColumn("softwareType", "jsonb", col => col.notNull()) + .addColumn("name", "text", col => col.unique().notNull()) + .addColumn("description", "text", col => col.notNull()) + .addColumn("license", "text", col => col.notNull()) + .addColumn("versionMin", "text") + .addColumn("isPresentInSupportContract", "boolean", col => col.notNull()) + .addColumn("isFromFrenchPublicService", "boolean", col => col.notNull()) + .addColumn("logoUrl", "text") + .addColumn("keywords", "jsonb", col => col.notNull()) + .addColumn("doRespectRgaa", "boolean") + .addColumn("isStillInObservation", "boolean", col => col.notNull()) + .addColumn("workshopUrls", "jsonb", col => col.notNull()) + .addColumn("categories", "jsonb", col => col.notNull()) + .addColumn("generalInfoMd", "text") + .addColumn("addedByUserId", "integer", col => col.notNull().references("users.id")) + .addColumn("dereferencing", "jsonb") + .addColumn("referencedSinceTime", "timestamptz", col => col.notNull()) + .addColumn("updateTime", "timestamptz", col => col.notNull()) + .execute(); + + // Create sources table + await db.schema + .createTable("sources") + .addColumn("slug", "text", col => col.primaryKey()) + .addColumn("kind", sql`external_data_origin_type`, col => col.notNull()) + .addColumn("url", "text", col => col.notNull()) + .addColumn("priority", "integer", col => col.unique().notNull()) + .addColumn("description", "jsonb") + .execute(); + + // Create software_external_datas table + await db.schema + .createTable("software_external_datas") + .addColumn("externalId", "text", col => col.notNull()) + .addColumn("sourceSlug", "text", col => col.notNull().references("sources.slug").onDelete("cascade")) + .addColumn("softwareId", "integer", col => col.references("softwares.id").onDelete("cascade")) + .addColumn("developers", "jsonb", col => col.notNull()) + .addColumn("label", "jsonb", col => col.notNull()) + .addColumn("description", "jsonb", col => col.notNull()) + .addColumn("isLibreSoftware", "boolean") + .addColumn("logoUrl", "text") + .addColumn("websiteUrl", "text") + .addColumn("sourceUrl", "text") + .addColumn("documentationUrl", "text") + .addColumn("license", "text") + .addColumn("softwareVersion", "text") + .addColumn("publicationTime", "timestamptz") + .addColumn("keywords", "jsonb") + .addColumn("programmingLanguages", "jsonb") + .addColumn("applicationCategories", "jsonb") + .addColumn("referencePublications", "jsonb") + .addColumn("identifiers", "jsonb") + .addColumn("lastDataFetchAt", "timestamptz") + .addColumn("providers", "jsonb") + .addPrimaryKeyConstraint("software_external_datas_pkey", ["externalId", "sourceSlug"]) + .addUniqueConstraint("uniq_source_external_id", ["sourceSlug", "externalId", "softwareId"]) + .execute(); + + // Create softwares__similar_software_external_datas table + await db.schema + .createTable("softwares__similar_software_external_datas") + .addColumn("softwareId", "integer", col => col.notNull().references("softwares.id").onDelete("cascade")) + .addColumn("similarExternalId", "text", col => col.notNull()) + .addColumn("sourceSlug", "text", col => col.notNull()) + .addPrimaryKeyConstraint("softwares__similar_software_external_datas_pkey", [ + "softwareId", + "similarExternalId", + "sourceSlug" + ]) + .addForeignKeyConstraint( + "softwares__similar_software_external_datas_software_external_da", + ["similarExternalId", "sourceSlug"], + "software_external_datas", + ["externalId", "sourceSlug"], + cb => cb.onDelete("cascade") + ) + .execute(); + + // Create software_users table + await db.schema + .createTable("software_users") + .addColumn("softwareId", "integer", col => col.notNull().references("softwares.id").onDelete("cascade")) + .addColumn("userId", "integer", col => col.notNull().references("users.id").onDelete("cascade")) + .addColumn("useCaseDescription", "text", col => col.notNull()) + .addColumn("os", "text") + .addColumn("version", "text", col => col.notNull()) + .addColumn("serviceUrl", "text") + .execute(); + + // Create software_referents table + await db.schema + .createTable("software_referents") + .addColumn("softwareId", "integer", col => col.notNull().references("softwares.id").onDelete("cascade")) + .addColumn("userId", "integer", col => col.notNull().references("users.id").onDelete("cascade")) + .addColumn("useCaseDescription", "text", col => col.notNull()) + .addColumn("isExpert", "boolean", col => col.notNull()) + .addColumn("serviceUrl", "text") + .execute(); + + // Create instances table + await db.schema + .createTable("instances") + .addColumn("id", "serial", col => col.primaryKey()) + .addColumn("mainSoftwareSillId", "integer", col => col.notNull().references("softwares.id").onDelete("cascade")) + .addColumn("addedByUserId", "integer", col => col.notNull().references("users.id")) + .addColumn("organization", "text", col => col.notNull()) + .addColumn("targetAudience", "text", col => col.notNull()) + .addColumn("instanceUrl", "text") + .addColumn("isPublic", "boolean", col => col.notNull()) + .addColumn("referencedSinceTime", "timestamptz", col => col.notNull()) + .addColumn("updateTime", "timestamptz", col => col.notNull()) + .execute(); + + // Create user_sessions table + await db.schema + .createTable("user_sessions") + .addColumn("id", "uuid", col => col.primaryKey()) + .addColumn("state", "text", col => col.notNull()) + .addColumn("redirectUrl", "text") + .addColumn("userId", "integer", col => col.references("users.id").onDelete("cascade")) + .addColumn("email", "text") + .addColumn("accessToken", "text") + .addColumn("refreshToken", "text") + .addColumn("idToken", "text") + .addColumn("expiresAt", "timestamptz") + .addColumn("createdAt", "timestamptz", col => col.notNull().defaultTo(sql`now()`)) + .addColumn("updatedAt", "timestamptz", col => col.notNull().defaultTo(sql`now()`)) + .addColumn("loggedOutAt", "timestamptz") + .execute(); + + // Create indexes + await db.schema + .createIndex("instances_mainSoftwareSillId_idx") + .on("instances") + .column("mainSoftwareSillId") + .execute(); + + await db.schema.createIndex("sessions_state_idx").on("user_sessions").column("state").execute(); + await db.schema.createIndex("sessions_userId_idx").on("user_sessions").column("userId").execute(); + await db.schema.createIndex("sessions_expiresAt_idx").on("user_sessions").column("expiresAt").execute(); + + await db.schema + .createIndex("softwareReferents_software_idx") + .on("software_referents") + .column("softwareId") + .execute(); + await db.schema.createIndex("softwareUsers_software_idx").on("software_users").column("softwareId").execute(); + + await db.schema + .createIndex("softwares_similarExternalId_idx") + .on("softwares__similar_software_external_datas") + .column("similarExternalId") + .execute(); + await db.schema + .createIndex("softwares_similarSoftwareId_idx") + .on("softwares__similar_software_external_datas") + .column("softwareId") + .execute(); + + console.log("Schema created successfully."); +} + +export async function down(db: Kysely): Promise { + // Drop tables in reverse order (respecting foreign key constraints) + await db.schema.dropTable("user_sessions").ifExists().execute(); + await db.schema.dropTable("instances").ifExists().execute(); + await db.schema.dropTable("software_referents").ifExists().execute(); + await db.schema.dropTable("software_users").ifExists().execute(); + await db.schema.dropTable("softwares__similar_software_external_datas").ifExists().execute(); + await db.schema.dropTable("software_external_datas").ifExists().execute(); + await db.schema.dropTable("softwares").ifExists().execute(); + await db.schema.dropTable("sources").ifExists().execute(); + await db.schema.dropTable("users").ifExists().execute(); + await db.schema.dropType("external_data_origin_type").ifExists().execute(); +}