From 572b190d1f291fbb83857602852926a6f72f80ad Mon Sep 17 00:00:00 2001 From: Onoko Date: Sun, 10 May 2026 12:10:00 +0200 Subject: [PATCH 1/3] feat: update testimony schema and repository to allow optional name and createdAt fields, and add insert column Co-authored-by: Copilot --- migrations/202605101156.ts | 35 ++++++++++++++++++++++ migrations/202605101159.ts | 29 ++++++++++++++++++ src/graphql/resolvers/testimonyResolver.ts | 3 +- src/graphql/schemas/testimonySchema.ts | 8 +++-- src/repositories/TestimonyRepository.ts | 6 ++-- src/types/testimonyTypes.ts | 5 ++-- 6 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 migrations/202605101156.ts create mode 100644 migrations/202605101159.ts diff --git a/migrations/202605101156.ts b/migrations/202605101156.ts new file mode 100644 index 0000000..7e51a30 --- /dev/null +++ b/migrations/202605101156.ts @@ -0,0 +1,35 @@ +import { Pool } from "mariadb/*"; +import { MigrationParams } from "umzug"; + +/** + * Migration pour définir une valeur par défaut vide sur la colonne "name" + * de la table "testimony" tout en la conservant NOT NULL. + */ + +export async function up({ context: pool }: MigrationParams) { + const conn = await pool.getConnection(); + try { + await conn.query( + `ALTER TABLE IF EXISTS testimony MODIFY COLUMN IF EXISTS name VARCHAR(255) NULL;`, + ); + await conn.query( + `ALTER TABLE IF EXISTS testimony MODIFY COLUMN IF EXISTS created_at VARCHAR(255) NULL;`, + ); + } finally { + conn.release(); + } +} + +export async function down({ context: pool }: MigrationParams) { + const conn = await pool.getConnection(); + try { + await conn.query( + `ALTER TABLE IF EXISTS testimony MODIFY COLUMN IF EXISTS name VARCHAR(255) NOT NULL;`, + ); + await conn.query( + `ALTER TABLE IF EXISTS testimony MODIFY COLUMN IF EXISTS created_at VARCHAR(255) NOT NULL;`, + ); + } finally { + conn.release(); + } +} diff --git a/migrations/202605101159.ts b/migrations/202605101159.ts new file mode 100644 index 0000000..c97013f --- /dev/null +++ b/migrations/202605101159.ts @@ -0,0 +1,29 @@ +import { Pool } from "mariadb/*"; +import { MigrationParams } from "umzug"; + +/** + * Migration pour définir une valeur par défaut vide sur la colonne "name" + * de la table "testimony" tout en la conservant NOT NULL. + */ + +export async function up({ context: pool }: MigrationParams) { + const conn = await pool.getConnection(); + try { + await conn.query( + "ALTER TABLE IF EXISTS testimony ADD COLUMN IF NOT EXISTS `insert` BOOLEAN;", + ); + } finally { + conn.release(); + } +} + +export async function down({ context: pool }: MigrationParams) { + const conn = await pool.getConnection(); + try { + await conn.query( + "ALTER TABLE IF EXISTS testimony DROP COLUMN IF EXISTS `insert`;", + ); + } finally { + conn.release(); + } +} diff --git a/src/graphql/resolvers/testimonyResolver.ts b/src/graphql/resolvers/testimonyResolver.ts index cf85e19..1bce0aa 100644 --- a/src/graphql/resolvers/testimonyResolver.ts +++ b/src/graphql/resolvers/testimonyResolver.ts @@ -43,9 +43,8 @@ const testimonyResolver = { ): Promise => { checkAuth(context); const input = { ..._args.input }; - if (isEmpty(input.name)) throw new Error("Name is required"); if (isEmpty(input.content)) throw new Error("Content is required"); - input.name = sanitizeString(input.name); + if (input.name) input.name = sanitizeString(input.name); if (input.company) input.company = sanitizeString(input.company); input.content = sanitizeWysiwyg(input.content); input.createdAt = new Date(input.createdAt || Date.now()); diff --git a/src/graphql/schemas/testimonySchema.ts b/src/graphql/schemas/testimonySchema.ts index 4c252fc..15428e1 100644 --- a/src/graphql/schemas/testimonySchema.ts +++ b/src/graphql/schemas/testimonySchema.ts @@ -2,18 +2,20 @@ export const testimonyTypes = ` type Testimony { id: ID! - name: String! + name: String company: String content: String! - createdAt: String! + createdAt: String + insert: Boolean } `; export const testimonyInputs = ` input TestimonyInput { - name: String! + name: String company: String content: String! createdAt: String + insert: Boolean } `; diff --git a/src/repositories/TestimonyRepository.ts b/src/repositories/TestimonyRepository.ts index 1ebfeae..024065f 100644 --- a/src/repositories/TestimonyRepository.ts +++ b/src/repositories/TestimonyRepository.ts @@ -16,7 +16,7 @@ export default class TestimonyRepository extends BaseRepository { async getAll(): Promise { return withConnection(this.pool, (conn) => conn.query( - `SELECT id, name, company, content, created_at AS createdAt FROM testimony ORDER BY created_at DESC`, + `SELECT id, name, company, content, created_at, \`insert\` FROM testimony ORDER BY created_at DESC`, ), ); } @@ -33,13 +33,14 @@ export default class TestimonyRepository extends BaseRepository { const id = this.generateId(); await withConnection(this.pool, (conn) => conn.query( - `INSERT INTO testimony (id, name, company, content, created_at) VALUES (?, ?, ?, ?, ?)`, + `INSERT INTO testimony (id, name, company, content, created_at, \`insert\`) VALUES (?, ?, ?, ?, ?, ?)`, [ id, testimony.name, testimony.company || null, testimony.content, testimony.createdAt || new Date().toISOString(), + testimony.insert || false, ], ), ); @@ -60,6 +61,7 @@ export default class TestimonyRepository extends BaseRepository { name: testimony.name || undefined, company: testimony.company || undefined, content: testimony.content || undefined, + insert: testimony.insert || undefined, }); } } diff --git a/src/types/testimonyTypes.ts b/src/types/testimonyTypes.ts index a3c0aee..f2806d1 100644 --- a/src/types/testimonyTypes.ts +++ b/src/types/testimonyTypes.ts @@ -1,8 +1,9 @@ // Interface représentant un témoignage export interface Testimony { id: string; - name: string; + name?: string; company?: string; content: string; - createdAt: Date; + createdAt?: Date; + insert?: boolean; } From 414b00ba07726e2f7b243f143d168c6299f68aa6 Mon Sep 17 00:00:00 2001 From: Onoko Date: Sun, 10 May 2026 12:34:21 +0200 Subject: [PATCH 2/3] feat: enhance update method to handle SQL conflict by using backticks for insert key Co-authored-by: Copilot --- src/repositories/TestimonyRepository.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/repositories/TestimonyRepository.ts b/src/repositories/TestimonyRepository.ts index 024065f..5977ecb 100644 --- a/src/repositories/TestimonyRepository.ts +++ b/src/repositories/TestimonyRepository.ts @@ -57,11 +57,15 @@ export default class TestimonyRepository extends BaseRepository { */ async update(testimony: Partial): Promise { if (!testimony.id) throw new Error("ID is required for update"); - return this.updateOne(testimony.id, { + // Utilise la clé '`insert`' pour éviter le conflit SQL + const updateData: Record = { name: testimony.name || undefined, company: testimony.company || undefined, content: testimony.content || undefined, - insert: testimony.insert || undefined, - }); + }; + if (typeof testimony.insert !== "undefined") { + updateData["`insert`"] = testimony.insert; + } + return this.updateOne(testimony.id, updateData); } } From 8bf59687ff11ee979e32245d479d5a37ecc25829 Mon Sep 17 00:00:00 2001 From: Onoko Date: Sun, 10 May 2026 17:33:34 +0200 Subject: [PATCH 3/3] feat: log retrieved testimonies and update SQL query to order by createdAt and name --- src/graphql/resolvers/testimonyResolver.ts | 4 +++- src/repositories/TestimonyRepository.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/graphql/resolvers/testimonyResolver.ts b/src/graphql/resolvers/testimonyResolver.ts index 1bce0aa..b1d4f7a 100644 --- a/src/graphql/resolvers/testimonyResolver.ts +++ b/src/graphql/resolvers/testimonyResolver.ts @@ -22,7 +22,9 @@ const testimonyResolver = { _args: Record, context: { testimonyRepo: TestimonyRepository }, ): Promise => { - return await context.testimonyRepo.getAll(); + const testimonies = await context.testimonyRepo.getAll(); + console.log("Retrieved testimonies:", testimonies); + return testimonies; }, /** diff --git a/src/repositories/TestimonyRepository.ts b/src/repositories/TestimonyRepository.ts index 5977ecb..666a600 100644 --- a/src/repositories/TestimonyRepository.ts +++ b/src/repositories/TestimonyRepository.ts @@ -16,7 +16,7 @@ export default class TestimonyRepository extends BaseRepository { async getAll(): Promise { return withConnection(this.pool, (conn) => conn.query( - `SELECT id, name, company, content, created_at, \`insert\` FROM testimony ORDER BY created_at DESC`, + `SELECT id, name, company, content, created_at AS createdAt, \`insert\` FROM testimony ORDER BY createdAt DESC, name DESC`, ), ); } @@ -62,6 +62,7 @@ export default class TestimonyRepository extends BaseRepository { name: testimony.name || undefined, company: testimony.company || undefined, content: testimony.content || undefined, + created_at: testimony.createdAt || undefined, }; if (typeof testimony.insert !== "undefined") { updateData["`insert`"] = testimony.insert;