diff --git a/config/cron-task.js b/config/cron-task.js index fc910a8..75799f7 100644 --- a/config/cron-task.js +++ b/config/cron-task.js @@ -1,5 +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: { @@ -19,4 +21,38 @@ module.exports = { tz: 'Indian/Reunion', }, }, + importBokanteComments: { + task: async ({ strapi }) => { + if (!process.env.BOKANTE_ACCESS_TOKEN) { + return; + } + + const {imported} = await importBokanteComments({strapi}); + + if (imported > 0) { + strapi.log.info(`Import bokante : ${imported} commentaire(s) importé(s).`); + } + }, + options: { + rule: '*/20 * * * *', + 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/api/commentaire/content-types/commentaire/schema.json b/src/api/commentaire/content-types/commentaire/schema.json index 4ffe8bc..c3887bb 100644 --- a/src/api/commentaire/content-types/commentaire/schema.json +++ b/src/api/commentaire/content-types/commentaire/schema.json @@ -29,6 +29,31 @@ "type": "relation", "relation": "oneToOne", "target": "api::parole.parole" + }, + "origine": { + "type": "enumeration", + "enum": ["local", "activitypub"], + "default": "local", + "required": true + }, + "auteurNom": { + "type": "string" + }, + "auteurHandle": { + "type": "string" + }, + "auteurAvatarUrl": { + "type": "string" + }, + "auteurProfilUrl": { + "type": "string" + }, + "remoteId": { + "type": "string", + "unique": true + }, + "remoteUrl": { + "type": "string" } } } 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 4d394e3..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,6 +118,113 @@ describe('beforeUpdate — notifications Telegram/Revolt', () => { }); }); +describe('afterCreate — publication miroir bokante', () => { + const originalEnv = {...process.env}; + const bokanteMastodon = require('../../../../../utils/bokante-mastodon'); + const originalCreateStatus = bokanteMastodon.createStatus; + + function buildStrapi() { + const dbQuery = { + findOne: vi.fn(async () => null), + updateMany: vi.fn() + }; + + return { + db: {query: vi.fn(() => dbQuery)}, + plugins: {email: {services: {email: {send: vi.fn()}}}}, + log: {error: vi.fn()} + }; + } + + function buildEvent(resultOverrides = {}) { + return { + 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 + } + }; + } + + afterEach(() => { + process.env = {...originalEnv}; + bokanteMastodon.createStatus = originalCreateStatus; + delete global.strapi; + }); + + 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 strapiMock = buildStrapi(); + const {afterCreate} = await loadLifecycles(strapiMock); + + await afterCreate(buildEvent()); + + expect(bokanteMastodon.createStatus).toHaveBeenCalledTimes(1); + expect(bokanteMastodon.createStatus.mock.calls[0][0]).toContain('Mon titre'); + 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 strapiMock = buildStrapi(); + const {afterCreate} = await loadLifecycles(strapiMock); + + await afterCreate(buildEvent({bokanteStatusId: '112233'})); + + expect(bokanteMastodon.createStatus).not.toHaveBeenCalled(); + }); + + it('ne publie rien si BOKANTE_ACCESS_TOKEN n\'est pas configuré', async () => { + delete process.env.BOKANTE_ACCESS_TOKEN; + bokanteMastodon.createStatus = vi.fn(async () => ({id: '999'})); + + const strapiMock = buildStrapi(); + const {afterCreate} = await loadLifecycles(strapiMock); + + await afterCreate(buildEvent()); + + expect(bokanteMastodon.createStatus).not.toHaveBeenCalled(); + }); + + 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 strapiMock = buildStrapi(); + const {afterCreate} = await loadLifecycles(strapiMock); + + await expect(afterCreate(buildEvent())).resolves.not.toThrow(); + + expect(strapiMock.log.error).toHaveBeenCalledWith(expect.stringContaining('bokante')); + expect(strapiMock.db.query('api::parole.parole').updateMany).not.toHaveBeenCalled(); + }); +}); + describe('beforeUpdate — createdBy/updatedBy', () => { afterEach(() => { delete global.strapi; diff --git a/src/api/parole/content-types/parole/lifecycles.js b/src/api/parole/content-types/parole/lifecycles.js index b0ce667..3718327 100644 --- a/src/api/parole/content-types/parole/lifecycles.js +++ b/src/api/parole/content-types/parole/lifecycles.js @@ -2,6 +2,7 @@ const slugify = require('slugify'); const axios = require('axios'); +const bokanteMastodon = require('../../../../utils/bokante-mastodon'); const utils = require('@strapi/utils'); const { ApplicationError } = utils.errors; @@ -272,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); diff --git a/src/api/parole/content-types/parole/schema.json b/src/api/parole/content-types/parole/schema.json index 0f6c54e..6083ba1 100644 --- a/src/api/parole/content-types/parole/schema.json +++ b/src/api/parole/content-types/parole/schema.json @@ -158,6 +158,9 @@ "type": "component", "component": "kit.lyen", "repeatable": true + }, + "bokanteStatusId": { + "type": "string" } } } 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/__tests__/bokante-mastodon.test.js b/src/utils/__tests__/bokante-mastodon.test.js new file mode 100644 index 0000000..8bf735a --- /dev/null +++ b/src/utils/__tests__/bokante-mastodon.test.js @@ -0,0 +1,73 @@ +import {describe, it, expect, vi, afterEach, beforeEach} from 'vitest'; +import {createStatus, getStatusContext} from '../bokante-mastodon.js'; + +describe('bokante-mastodon', () => { + const originalFetch = global.fetch; + const originalEnv = {...process.env}; + + beforeEach(() => { + process.env.BOKANTE_INSTANCE_URL = 'https://bokante.o-k-i.net'; + process.env.BOKANTE_ACCESS_TOKEN = 'test-token'; + }); + + afterEach(() => { + global.fetch = originalFetch; + process.env = {...originalEnv}; + vi.restoreAllMocks(); + }); + + describe('createStatus', () => { + it('poste le statut avec le bon endpoint, jeton et corps', async () => { + global.fetch = vi.fn(async () => ({ + ok: true, + json: async () => ({id: '112233', url: 'https://bokante.o-k-i.net/@paroles/112233'}) + })); + + const result = await createStatus('Nouvelle parole : "Titre"'); + + const [url, options] = global.fetch.mock.calls[0]; + expect(url).toBe('https://bokante.o-k-i.net/api/v1/statuses'); + expect(options.method).toBe('POST'); + expect(options.headers.Authorization).toBe('Bearer test-token'); + expect(JSON.parse(options.body)).toEqual({ + status: 'Nouvelle parole : "Titre"', + visibility: 'public' + }); + expect(result).toEqual({id: '112233', url: 'https://bokante.o-k-i.net/@paroles/112233'}); + }); + + it('lève une erreur claire sur réponse HTTP non-ok', async () => { + global.fetch = vi.fn(async () => ({ + ok: false, + status: 422, + text: async () => 'Validation Failed' + })); + + await expect(createStatus('texte')).rejects.toThrow('Mastodon 422: Validation Failed'); + }); + }); + + describe('getStatusContext', () => { + it('récupère le contexte du statut avec le bon endpoint et jeton', async () => { + const context = {ancestors: [], descendants: [{id: '1', content: '

coucou

'}]}; + global.fetch = vi.fn(async () => ({ok: true, json: async () => context})); + + const result = await getStatusContext('112233'); + + const [url, options] = global.fetch.mock.calls[0]; + expect(url).toBe('https://bokante.o-k-i.net/api/v1/statuses/112233/context'); + expect(options.headers.Authorization).toBe('Bearer test-token'); + expect(result).toEqual(context); + }); + + it('lève une erreur claire sur réponse HTTP non-ok', async () => { + global.fetch = vi.fn(async () => ({ + ok: false, + status: 404, + text: async () => 'Record not found' + })); + + await expect(getStatusContext('inconnu')).rejects.toThrow('Mastodon 404: Record not found'); + }); + }); +}); diff --git a/src/utils/__tests__/import-bokante-comments.test.js b/src/utils/__tests__/import-bokante-comments.test.js new file mode 100644 index 0000000..14384aa --- /dev/null +++ b/src/utils/__tests__/import-bokante-comments.test.js @@ -0,0 +1,131 @@ +import {describe, it, expect, vi, afterEach} from 'vitest'; +import {importBokanteComments} from '../import-bokante-comments.js'; + +const bokanteMastodon = require('../bokante-mastodon.js'); + +function buildStatus(overrides = {}) { + return { + id: 'status-1', + content: '

Trè bèl parol !

', + created_at: '2026-07-04T12:00:00.000Z', + url: 'https://bokante.o-k-i.net/@quelqun/status-1', + account: { + display_name: 'Quelqu\'un', + acct: 'quelqun@mastodon.social', + avatar: 'https://mastodon.social/avatars/quelqun.png', + url: 'https://mastodon.social/@quelqun' + }, + ...overrides + }; +} + +function buildStrapi({paroles, existingRemoteIds = [], createImpl}) { + const commentaireDocuments = { + create: createImpl || vi.fn(async ({data}) => ({id: Math.floor(Math.random() * 10_000), ...data})) + }; + const paroleDocuments = {update: vi.fn(async () => ({}))}; + + return { + db: { + query: uid => { + if (uid === 'api::parole.parole') { + return {findMany: vi.fn(async () => paroles)}; + } + + if (uid === 'api::commentaire.commentaire') { + return { + findOne: vi.fn(async ({where}) => (existingRemoteIds.includes(where.remoteId) ? {id: 1} : null)) + }; + } + + return {}; + } + }, + documents: uid => { + if (uid === 'api::commentaire.commentaire') return commentaireDocuments; + if (uid === 'api::parole.parole') return paroleDocuments; + return {}; + }, + log: {error: vi.fn()} + }; +} + +describe('importBokanteComments', () => { + const originalGetStatusContext = bokanteMastodon.getStatusContext; + + afterEach(() => { + bokanteMastodon.getStatusContext = originalGetStatusContext; + }); + + it('importe les nouveaux commentaires et les connecte à la parole', async () => { + bokanteMastodon.getStatusContext = vi.fn(async () => ({descendants: [buildStatus()]})); + const paroles = [{id: 7, documentId: 'doc-7', slug: 'mon-titre', bokanteStatusId: '112233'}]; + const strapi = buildStrapi({paroles}); + + const result = await importBokanteComments({strapi}); + + expect(bokanteMastodon.getStatusContext).toHaveBeenCalledWith('112233'); + expect(strapi.documents('api::commentaire.commentaire').create).toHaveBeenCalledWith({ + data: expect.objectContaining({ + contenu: 'Trè bèl parol !', + origine: 'activitypub', + auteurNom: 'Quelqu\'un', + auteurHandle: '@quelqun@mastodon.social', + auteurAvatarUrl: 'https://mastodon.social/avatars/quelqun.png', + auteurProfilUrl: 'https://mastodon.social/@quelqun', + remoteId: 'status-1', + remoteUrl: 'https://bokante.o-k-i.net/@quelqun/status-1', + parole: 7 + }) + }); + expect(strapi.documents('api::parole.parole').update).toHaveBeenCalledWith({ + documentId: 'doc-7', + data: {commentaires: {connect: expect.any(Array)}} + }); + expect(result.imported).toBe(1); + }); + + it('n\'importe pas un commentaire déjà présent (déduplication par remoteId)', async () => { + bokanteMastodon.getStatusContext = vi.fn(async () => ({descendants: [buildStatus({id: 'deja-la'})]})); + const paroles = [{id: 7, documentId: 'doc-7', slug: 'mon-titre', bokanteStatusId: '112233'}]; + const strapi = buildStrapi({paroles, existingRemoteIds: ['deja-la']}); + + const result = await importBokanteComments({strapi}); + + expect(strapi.documents('api::commentaire.commentaire').create).not.toHaveBeenCalled(); + expect(strapi.documents('api::parole.parole').update).not.toHaveBeenCalled(); + expect(result.imported).toBe(0); + }); + + it('ignore les statuts marqués sensitive', async () => { + bokanteMastodon.getStatusContext = vi.fn(async () => ({descendants: [buildStatus({sensitive: true})]})); + const paroles = [{id: 7, documentId: 'doc-7', slug: 'mon-titre', bokanteStatusId: '112233'}]; + const strapi = buildStrapi({paroles}); + + const result = await importBokanteComments({strapi}); + + expect(strapi.documents('api::commentaire.commentaire').create).not.toHaveBeenCalled(); + expect(result.imported).toBe(0); + }); + + it('continue les autres paroles quand une requête bokante échoue', async () => { + bokanteMastodon.getStatusContext = vi.fn(async statusId => { + if (statusId === 'echoue') { + throw new Error('Mastodon 500: boom'); + } + + return {descendants: [buildStatus({id: 'ok-1'})]}; + }); + + const paroles = [ + {id: 1, documentId: 'doc-1', slug: 'echoue', bokanteStatusId: 'echoue'}, + {id: 2, documentId: 'doc-2', slug: 'reussi', bokanteStatusId: 'ok'} + ]; + const strapi = buildStrapi({paroles}); + + const result = await importBokanteComments({strapi}); + + expect(strapi.log.error).toHaveBeenCalledWith(expect.stringContaining('echoue')); + expect(result.imported).toBe(1); + }); +}); 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}; diff --git a/src/utils/bokante-mastodon.js b/src/utils/bokante-mastodon.js new file mode 100644 index 0000000..362ef8f --- /dev/null +++ b/src/utils/bokante-mastodon.js @@ -0,0 +1,44 @@ +'use strict'; + +function getConfig() { + return { + instanceUrl: process.env.BOKANTE_INSTANCE_URL || 'https://bokante.o-k-i.net', + accessToken: process.env.BOKANTE_ACCESS_TOKEN + }; +} + +async function mastodonFetch(url, options) { + const {accessToken} = getConfig(); + const response = await fetch(url, { + ...options, + headers: { + Authorization: `Bearer ${accessToken}`, + ...options.headers + } + }); + + if (!response.ok) { + const body = await response.text(); + throw new Error(`Mastodon ${response.status}: ${body}`); + } + + return response.json(); +} + +async function createStatus(text, {visibility = 'public'} = {}) { + const {instanceUrl} = getConfig(); + return mastodonFetch(`${instanceUrl}/api/v1/statuses`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({status: text, visibility}) + }); +} + +async function getStatusContext(statusId) { + const {instanceUrl} = getConfig(); + return mastodonFetch(`${instanceUrl}/api/v1/statuses/${statusId}/context`, { + method: 'GET' + }); +} + +module.exports = {createStatus, getStatusContext}; diff --git a/src/utils/import-bokante-comments.js b/src/utils/import-bokante-comments.js new file mode 100644 index 0000000..f2d9676 --- /dev/null +++ b/src/utils/import-bokante-comments.js @@ -0,0 +1,69 @@ +'use strict'; + +const bokanteMastodon = require('./bokante-mastodon'); + +function stripHtml(html) { + return (html || '').replace(/<[^>]*>/g, '').trim(); +} + +async function importBokanteComments({strapi}) { + const paroles = await strapi.db.query('api::parole.parole').findMany({ + where: {bokanteStatusId: {$notNull: true}} + }); + + let imported = 0; + + for (const parole of paroles) { + let context; + try { + context = await bokanteMastodon.getStatusContext(parole.bokanteStatusId); + } catch (err) { + strapi.log.error(`Import bokante (${parole.slug}) : ${err.message}`); + continue; + } + + const newCommentIds = []; + + for (const status of context.descendants || []) { + if (status.sensitive) { + continue; + } + + const exists = await strapi.db.query('api::commentaire.commentaire').findOne({ + where: {remoteId: status.id} + }); + if (exists) { + continue; + } + + const commentaire = await strapi.documents('api::commentaire.commentaire').create({ + data: { + contenu: stripHtml(status.content), + datePublication: status.created_at, + origine: 'activitypub', + auteurNom: status.account?.display_name, + auteurHandle: status.account?.acct ? `@${status.account.acct}` : undefined, + auteurAvatarUrl: status.account?.avatar, + auteurProfilUrl: status.account?.url, + remoteId: status.id, + remoteUrl: status.url, + parole: parole.id + } + }); + + newCommentIds.push(commentaire.id); + imported += 1; + } + + if (newCommentIds.length > 0) { + await strapi.documents('api::parole.parole').update({ + documentId: parole.documentId, + data: {commentaires: {connect: newCommentIds}} + }); + } + } + + return {imported}; +} + +module.exports = {importBokanteComments}; diff --git a/types/generated/contentTypes.d.ts b/types/generated/contentTypes.d.ts index 527838e..da8a7d3 100644 --- a/types/generated/contentTypes.d.ts +++ b/types/generated/contentTypes.d.ts @@ -488,6 +488,10 @@ export interface ApiCommentaireCommentaire extends Struct.CollectionTypeSchema { draftAndPublish: true; }; attributes: { + auteurAvatarUrl: Schema.Attribute.String; + auteurHandle: Schema.Attribute.String; + auteurNom: Schema.Attribute.String; + auteurProfilUrl: Schema.Attribute.String; contenu: Schema.Attribute.RichText & Schema.Attribute.Required; createdAt: Schema.Attribute.DateTime; createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & @@ -499,8 +503,13 @@ export interface ApiCommentaireCommentaire extends Struct.CollectionTypeSchema { 'api::commentaire.commentaire' > & Schema.Attribute.Private; + origine: Schema.Attribute.Enumeration<['local', 'activitypub']> & + Schema.Attribute.Required & + Schema.Attribute.DefaultTo<'local'>; parole: Schema.Attribute.Relation<'oneToOne', 'api::parole.parole'>; publishedAt: Schema.Attribute.DateTime; + remoteId: Schema.Attribute.String & Schema.Attribute.Unique; + remoteUrl: Schema.Attribute.String; updatedAt: Schema.Attribute.DateTime; updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; @@ -526,6 +535,7 @@ export interface ApiParoleParole extends Struct.CollectionTypeSchema { attributes: { annee: Schema.Attribute.Integer; artistes: Schema.Attribute.Relation<'manyToMany', 'api::artiste.artiste'>; + bokanteStatusId: Schema.Attribute.String; commentaires: Schema.Attribute.Relation< 'oneToMany', 'api::commentaire.commentaire'