Bouton PDF par chapitre, badges morceaux améliorés, PDF configurable admin, git sync admin→prod
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Bouton PDF blanc par chapitre avec numéro de page (ChapterHeader) - Badges morceaux plus visibles (bordure, poids, hover) dans ChapterHeader et SongBadges - PDF viewer : page cible + panneau signets ouverts par défaut (BookPdfReader) - Config YAML : pdfFile dans book, chapterPages pour le mapping chapitre→page - Admin book : section PDF du livre avec chemin éditable et sauvegarde - Git sync automatique : chaque sauvegarde admin commit+push en prod (ADMIN_GIT_SYNC=true) - Docker : git installé en prod, volumes pour .git/site/content/public Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ export default defineEventHandler(async (event) => {
|
||||
const filePath = join(process.cwd(), 'content', 'book', `${slug}.md`)
|
||||
|
||||
await unlink(filePath)
|
||||
gitSyncContent(`Suppression chapitre ${slug}`, [`content/book/${slug}.md`])
|
||||
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
@@ -18,6 +18,7 @@ export default defineEventHandler(async (event) => {
|
||||
const content = `---\n${body.frontmatter.trim()}\n---\n${body.body}`
|
||||
|
||||
await writeFile(filePath, content, 'utf-8')
|
||||
gitSyncContent(`Mise à jour chapitre ${slug}`, [`content/book/${slug}.md`])
|
||||
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
@@ -24,6 +24,7 @@ readingTime: "5 min"
|
||||
`
|
||||
|
||||
await writeFile(filePath, content, 'utf-8')
|
||||
gitSyncContent(`Nouveau chapitre ${body.slug}`, [`content/book/${body.slug}.md`])
|
||||
|
||||
return { ok: true, slug: body.slug }
|
||||
})
|
||||
|
||||
@@ -25,5 +25,7 @@ export default defineEventHandler(async (event) => {
|
||||
}),
|
||||
)
|
||||
|
||||
gitSyncContent('Réorganisation chapitres', ['content/book/'])
|
||||
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event)
|
||||
await writeYaml('bookplayer.config.yml', body)
|
||||
gitSyncContent('Mise à jour config livre/player', ['site/bookplayer.config.yml'])
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
@@ -7,5 +7,6 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
const body = await readBody(event)
|
||||
await writeYaml(`pages/${name}.yml`, body)
|
||||
gitSyncContent(`Mise à jour page ${name}`, [`site/pages/${name}.yml`])
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event)
|
||||
await writeYaml('site.yml', body)
|
||||
gitSyncContent('Mise à jour config site', ['site/site.yml'])
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
@@ -23,6 +23,7 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
try {
|
||||
await unlink(filePath)
|
||||
gitSyncContent(`Suppression média ${path}`, [`public/${path}`])
|
||||
return { ok: true }
|
||||
}
|
||||
catch {
|
||||
|
||||
@@ -33,5 +33,9 @@ export default defineEventHandler(async (event) => {
|
||||
uploaded.push(`/${subdir}/${safeName}`)
|
||||
}
|
||||
|
||||
if (uploaded.length > 0) {
|
||||
gitSyncContent(`Upload média : ${uploaded.join(', ')}`, uploaded.map(f => `public${f}`))
|
||||
}
|
||||
|
||||
return { ok: true, files: uploaded }
|
||||
})
|
||||
|
||||
@@ -14,6 +14,7 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
data.messages.splice(index, 1)
|
||||
await writeYaml('messages.yml', data)
|
||||
gitSyncContent(`Suppression message #${id}`, ['site/messages.yml'])
|
||||
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
@@ -18,6 +18,7 @@ export default defineEventHandler(async (event) => {
|
||||
if (body.author !== undefined) message.author = body.author
|
||||
|
||||
await writeYaml('messages.yml', data)
|
||||
gitSyncContent(`Mise à jour message #${id}`, ['site/messages.yml'])
|
||||
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
44
server/utils/gitSync.ts
Normal file
44
server/utils/gitSync.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { execFile } from 'node:child_process'
|
||||
import { promisify } from 'node:util'
|
||||
|
||||
const exec = promisify(execFile)
|
||||
|
||||
const enabled = process.env.ADMIN_GIT_SYNC === 'true'
|
||||
const cwd = process.cwd()
|
||||
|
||||
async function git(...args: string[]): Promise<string> {
|
||||
const { stdout } = await exec('git', args, { cwd })
|
||||
return stdout.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit et push les modifications admin vers le dépôt git.
|
||||
* Activé uniquement si ADMIN_GIT_SYNC=true (prod).
|
||||
* Ne bloque jamais la réponse HTTP — exécution fire-and-forget.
|
||||
*/
|
||||
export function gitSyncContent(description: string, paths: string[] = ['.']) {
|
||||
if (!enabled) return
|
||||
|
||||
const run = async () => {
|
||||
try {
|
||||
for (const p of paths) {
|
||||
await git('add', p)
|
||||
}
|
||||
|
||||
// Vérifier s'il y a vraiment des changements stagés
|
||||
const status = await git('diff', '--cached', '--name-only')
|
||||
if (!status) return
|
||||
|
||||
await git('commit', '-m', `[admin] ${description}`)
|
||||
await git('push')
|
||||
|
||||
console.log(`[gitSync] pushed: ${description}`)
|
||||
}
|
||||
catch (err: any) {
|
||||
console.error(`[gitSync] error:`, err.message ?? err)
|
||||
}
|
||||
}
|
||||
|
||||
// Fire-and-forget : ne pas bloquer la réponse HTTP
|
||||
run()
|
||||
}
|
||||
Reference in New Issue
Block a user