diff --git a/src/api/parole/content-types/parole/__tests__/lifecycles.test.js b/src/api/parole/content-types/parole/__tests__/lifecycles.test.js index 10b2c3a..e6252d1 100644 --- a/src/api/parole/content-types/parole/__tests__/lifecycles.test.js +++ b/src/api/parole/content-types/parole/__tests__/lifecycles.test.js @@ -118,14 +118,14 @@ describe('beforeUpdate — notifications Telegram/Revolt', () => { }); }); -describe('beforeUpdate — publication miroir bokante', () => { +describe('afterCreate — publication miroir bokante', () => { const originalEnv = {...process.env}; const bokanteMastodon = require('../../../../../utils/bokante-mastodon'); const originalCreateStatus = bokanteMastodon.createStatus; - function buildStrapi(previous) { + function buildStrapi() { const dbQuery = { - findOne: vi.fn(async () => previous), + findOne: vi.fn(async () => null), updateMany: vi.fn() }; @@ -136,11 +136,16 @@ describe('beforeUpdate — publication miroir bokante', () => { }; } - function buildEvent(overrides = {}) { + function buildEvent(resultOverrides = {}) { return { - state: {}, - params: { - data: {documentId: 'doc-1', publishedAt: '2026-07-04T00:00:00.000Z', ...overrides} + params: {data: {titre: 'Mon titre'}}, + result: { + documentId: 'doc-1', + titre: 'Mon titre', + slug: 'mon-titre', + publishedAt: '2026-07-04T00:00:00.000Z', + bokanteStatusId: null, + ...resultOverrides } }; } @@ -151,33 +156,43 @@ describe('beforeUpdate — publication miroir bokante', () => { delete global.strapi; }); - it('publie un statut miroir et stocke bokanteStatusId sur data', async () => { + it('publie un statut miroir et synchronise bokanteStatusId sur le documentId', async () => { process.env.BOKANTE_ACCESS_TOKEN = 'fake-token'; bokanteMastodon.createStatus = vi.fn(async () => ({id: '112233'})); - const previous = {publishedAt: null, slug: 'mon-titre', titre: 'Mon titre', user: null, userAdmin: null, artistes: [], bokanteStatusId: null}; - const strapiMock = buildStrapi(previous); + const strapiMock = buildStrapi(); + const {afterCreate} = await loadLifecycles(strapiMock); - const {beforeUpdate} = await loadLifecycles(strapiMock); - const event = buildEvent(); - - await beforeUpdate(event); + await afterCreate(buildEvent()); expect(bokanteMastodon.createStatus).toHaveBeenCalledTimes(1); expect(bokanteMastodon.createStatus.mock.calls[0][0]).toContain('Mon titre'); - expect(event.params.data.bokanteStatusId).toBe('112233'); + expect(strapiMock.db.query('api::parole.parole').updateMany).toHaveBeenCalledWith({ + where: {documentId: 'doc-1'}, + data: {bokanteStatusId: '112233'} + }); + }); + + it('ne publie rien si la ligne créée n\'est pas publiée (simple brouillon)', async () => { + process.env.BOKANTE_ACCESS_TOKEN = 'fake-token'; + bokanteMastodon.createStatus = vi.fn(async () => ({id: '999'})); + + const strapiMock = buildStrapi(); + const {afterCreate} = await loadLifecycles(strapiMock); + + await afterCreate(buildEvent({publishedAt: null})); + + expect(bokanteMastodon.createStatus).not.toHaveBeenCalled(); }); it('ne republie pas si bokanteStatusId existe déjà', async () => { process.env.BOKANTE_ACCESS_TOKEN = 'fake-token'; bokanteMastodon.createStatus = vi.fn(async () => ({id: '999'})); - const previous = {publishedAt: null, slug: 'mon-titre', titre: 'Mon titre', user: null, userAdmin: null, artistes: [], bokanteStatusId: '112233'}; - const strapiMock = buildStrapi(previous); + const strapiMock = buildStrapi(); + const {afterCreate} = await loadLifecycles(strapiMock); - const {beforeUpdate} = await loadLifecycles(strapiMock); - - await beforeUpdate(buildEvent()); + await afterCreate(buildEvent({bokanteStatusId: '112233'})); expect(bokanteMastodon.createStatus).not.toHaveBeenCalled(); }); @@ -186,32 +201,27 @@ describe('beforeUpdate — publication miroir bokante', () => { delete process.env.BOKANTE_ACCESS_TOKEN; bokanteMastodon.createStatus = vi.fn(async () => ({id: '999'})); - const previous = {publishedAt: null, slug: 'mon-titre', titre: 'Mon titre', user: null, userAdmin: null, artistes: [], bokanteStatusId: null}; - const strapiMock = buildStrapi(previous); + const strapiMock = buildStrapi(); + const {afterCreate} = await loadLifecycles(strapiMock); - const {beforeUpdate} = await loadLifecycles(strapiMock); - - await beforeUpdate(buildEvent()); + await afterCreate(buildEvent()); expect(bokanteMastodon.createStatus).not.toHaveBeenCalled(); }); - it('n\'interrompt pas la publication quand bokante échoue', async () => { + it('n\'interrompt pas la création quand bokante échoue', async () => { process.env.BOKANTE_ACCESS_TOKEN = 'fake-token'; bokanteMastodon.createStatus = vi.fn(async () => { throw new Error('boom'); }); - const previous = {publishedAt: null, slug: 'mon-titre', titre: 'Mon titre', user: null, userAdmin: null, artistes: [], bokanteStatusId: null}; - const strapiMock = buildStrapi(previous); + const strapiMock = buildStrapi(); + const {afterCreate} = await loadLifecycles(strapiMock); - const {beforeUpdate} = await loadLifecycles(strapiMock); - const event = buildEvent(); - - await expect(beforeUpdate(event)).resolves.not.toThrow(); + await expect(afterCreate(buildEvent())).resolves.not.toThrow(); expect(strapiMock.log.error).toHaveBeenCalledWith(expect.stringContaining('bokante')); - expect(event.params.data.bokanteStatusId).toBeUndefined(); + expect(strapiMock.db.query('api::parole.parole').updateMany).not.toHaveBeenCalled(); }); }); diff --git a/src/api/parole/content-types/parole/lifecycles.js b/src/api/parole/content-types/parole/lifecycles.js index 0d5abc5..3718327 100644 --- a/src/api/parole/content-types/parole/lifecycles.js +++ b/src/api/parole/content-types/parole/lifecycles.js @@ -192,17 +192,6 @@ module.exports = { const previousPublishedAt = previousData.publishedAt; const currentPublished_at = data.publishedAt; if (currentPublished_at != previousPublishedAt) { - if (!previousData.bokanteStatusId && process.env.BOKANTE_ACCESS_TOKEN) { - try { - const status = await bokanteMastodon.createStatus( - `"${previousData.titre}" — nouvelle parole sur pawol.nu\n${process.env.WEBSITE_URL}/paroles/${previousData.slug}` - ); - data.bokanteStatusId = status.id; - } catch (err) { - strapi.log.error(`Publication bokante : ${err.message}`); - } - } - const message = `Nouvelle publication ❤️ \n${process.env.WEBSITE_URL}/paroles/${previousData.slug}`; if (previousData.user) { @@ -284,6 +273,27 @@ module.exports = { }, afterCreate: async event => { const {data} = event.params; + + // Avec draftAndPublish, "publier" crée une nouvelle ligne (la version publiée) + // au lieu de mettre à jour la ligne existante : c'est ici, et non dans + // beforeUpdate, qu'un événement de publication est détectable. + if (event.result?.publishedAt && !event.result?.bokanteStatusId && process.env.BOKANTE_ACCESS_TOKEN) { + try { + const status = await bokanteMastodon.createStatus( + `"${event.result.titre}" — nouvelle parole sur pawol.nu\n${process.env.WEBSITE_URL}/paroles/${event.result.slug}` + ); + // Synchronise brouillon et version publiée pour éviter une republication + // en double au prochain cycle dépublier/republier (qui recrée la ligne + // publiée à partir du brouillon). + await strapi.db.query('api::parole.parole').updateMany({ + where: {documentId: event.result.documentId}, + data: {bokanteStatusId: status.id} + }); + } catch (err) { + strapi.log.error(`Publication bokante : ${err.message}`); + } + } + const user = await jwennUserEpiId(data?.user?.id); const userAdmin = await jwennUserAdminEpiId(data?.createdBy); const superAdmin = await jwennSuperAdminEpiId(data?.createdBy);