diff --git a/config/cron-task.js b/config/cron-task.js index d6bd687..75799f7 100644 --- a/config/cron-task.js +++ b/config/cron-task.js @@ -1,6 +1,7 @@ const path = require('path'); const {backupDatabase} = require('../src/utils/backup-database'); const {importBokanteComments} = require('../src/utils/import-bokante-comments'); +const {backfillBokanteMirror} = require('../src/utils/backfill-bokante-mirrors'); module.exports = { myJob: { @@ -37,4 +38,21 @@ module.exports = { tz: 'Indian/Reunion', }, }, + backfillBokanteMirror: { + task: async ({ strapi }) => { + if (!process.env.BOKANTE_ACCESS_TOKEN) { + return; + } + + const {backfilled, slug} = await backfillBokanteMirror({strapi}); + + if (backfilled) { + strapi.log.info(`Backfill bokante : parole "${slug}" publiée.`); + } + }, + options: { + rule: process.env.BOKANTE_BACKFILL_CRON || '0 * * * *', + tz: 'Indian/Reunion', + }, + }, }; diff --git a/src/utils/__tests__/backfill-bokante-mirrors.test.js b/src/utils/__tests__/backfill-bokante-mirrors.test.js new file mode 100644 index 0000000..478df28 --- /dev/null +++ b/src/utils/__tests__/backfill-bokante-mirrors.test.js @@ -0,0 +1,75 @@ +import {describe, it, expect, vi, afterEach} from 'vitest'; +import {backfillBokanteMirror} from '../backfill-bokante-mirrors.js'; + +const bokanteMastodon = require('../bokante-mastodon.js'); + +function buildStrapi({paroles = []} = {}) { + const updateMany = vi.fn(); + + return { + db: { + query: () => ({ + findMany: vi.fn(async ({where, orderBy, limit}) => { + expect(where).toEqual({ + publishedAt: {$notNull: true}, + bokanteStatusId: {$null: true} + }); + expect(orderBy).toEqual({publishedAt: 'asc'}); + expect(limit).toBe(1); + return paroles.slice(0, limit); + }), + updateMany + }) + }, + log: {error: vi.fn(), info: vi.fn()} + }; +} + +describe('backfillBokanteMirror', () => { + const originalCreateStatus = bokanteMastodon.createStatus; + + afterEach(() => { + bokanteMastodon.createStatus = originalCreateStatus; + }); + + it('publie le miroir pour la parole publiée la plus ancienne sans bokanteStatusId', async () => { + bokanteMastodon.createStatus = vi.fn(async () => ({id: '112233'})); + const parole = {documentId: 'doc-1', titre: 'Vieux titre', slug: 'vieux-titre'}; + const strapi = buildStrapi({paroles: [parole]}); + + const result = await backfillBokanteMirror({strapi}); + + expect(bokanteMastodon.createStatus).toHaveBeenCalledTimes(1); + expect(bokanteMastodon.createStatus.mock.calls[0][0]).toContain('Vieux titre'); + expect(bokanteMastodon.createStatus.mock.calls[0][0]).not.toContain('nouvelle parole'); + expect(strapi.db.query().updateMany).toHaveBeenCalledWith({ + where: {documentId: 'doc-1'}, + data: {bokanteStatusId: '112233'} + }); + expect(result).toEqual({backfilled: true, slug: 'vieux-titre'}); + }); + + it('ne fait rien si le catalogue est déjà rattrapé', async () => { + bokanteMastodon.createStatus = vi.fn(); + const strapi = buildStrapi({paroles: []}); + + const result = await backfillBokanteMirror({strapi}); + + expect(bokanteMastodon.createStatus).not.toHaveBeenCalled(); + expect(result).toEqual({backfilled: false}); + }); + + it('n\'interrompt rien si bokante échoue, et ne marque pas la parole comme traitée', async () => { + bokanteMastodon.createStatus = vi.fn(async () => { + throw new Error('boom'); + }); + const parole = {documentId: 'doc-1', titre: 'Titre', slug: 'titre'}; + const strapi = buildStrapi({paroles: [parole]}); + + const result = await backfillBokanteMirror({strapi}); + + expect(strapi.log.error).toHaveBeenCalledWith(expect.stringContaining('titre')); + expect(strapi.db.query().updateMany).not.toHaveBeenCalled(); + expect(result).toEqual({backfilled: false}); + }); +}); diff --git a/src/utils/backfill-bokante-mirrors.js b/src/utils/backfill-bokante-mirrors.js new file mode 100644 index 0000000..17441c8 --- /dev/null +++ b/src/utils/backfill-bokante-mirrors.js @@ -0,0 +1,36 @@ +'use strict'; + +const bokanteMastodon = require('./bokante-mastodon'); + +async function backfillBokanteMirror({strapi}) { + const [parole] = await strapi.db.query('api::parole.parole').findMany({ + where: { + publishedAt: {$notNull: true}, + bokanteStatusId: {$null: true} + }, + orderBy: {publishedAt: 'asc'}, + limit: 1 + }); + + if (!parole) { + return {backfilled: false}; + } + + try { + const status = await bokanteMastodon.createStatus( + `"${parole.titre}" — à (re)découvrir sur pawol.nu\n${process.env.WEBSITE_URL}/paroles/${parole.slug}` + ); + + await strapi.db.query('api::parole.parole').updateMany({ + where: {documentId: parole.documentId}, + data: {bokanteStatusId: status.id} + }); + + return {backfilled: true, slug: parole.slug}; + } catch (err) { + strapi.log.error(`Backfill bokante (${parole.slug}) : ${err.message}`); + return {backfilled: false}; + } +} + +module.exports = {backfillBokanteMirror};