Audit sécurité/qualité : corrections critiques, tests, CI et lint #4
@@ -70,6 +70,7 @@ $RECYCLE.BIN/
|
|||||||
*.sql
|
*.sql
|
||||||
*.sqlite
|
*.sqlite
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
backups/
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
|
|||||||
+14
-1
@@ -1,6 +1,19 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const {backupDatabase} = require('../src/utils/backup-database');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
myJob: {
|
myJob: {
|
||||||
task: ({ strapi }) => {console.log('TODO > save db')},
|
task: ({ strapi }) => {
|
||||||
|
const dbPath = path.join(__dirname, '..', process.env.DATABASE_FILENAME || '.tmp/data.db');
|
||||||
|
const backupsDir = path.join(__dirname, '..', 'backups');
|
||||||
|
const backupPath = backupDatabase({dbPath, backupsDir});
|
||||||
|
|
||||||
|
if (backupPath) {
|
||||||
|
strapi.log.info(`Sauvegarde de la base effectuée : ${backupPath}`);
|
||||||
|
} else {
|
||||||
|
strapi.log.warn(`Sauvegarde de la base ignorée : fichier introuvable (${dbPath})`);
|
||||||
|
}
|
||||||
|
},
|
||||||
options: {
|
options: {
|
||||||
rule: '0 0 * * SUN',
|
rule: '0 0 * * SUN',
|
||||||
tz: 'Indian/Reunion',
|
tz: 'Indian/Reunion',
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import {describe, it, expect, afterEach} from 'vitest'
|
||||||
|
import fs from 'fs'
|
||||||
|
import os from 'os'
|
||||||
|
import path from 'path'
|
||||||
|
import {backupDatabase} from '../backup-database.js'
|
||||||
|
|
||||||
|
const tmpDirs = []
|
||||||
|
|
||||||
|
function makeTmpDir() {
|
||||||
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'backup-database-test-'))
|
||||||
|
tmpDirs.push(dir)
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('backupDatabase', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
for (const dir of tmpDirs.splice(0)) {
|
||||||
|
fs.rmSync(dir, {recursive: true, force: true})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("ne fait rien quand le fichier de base n'existe pas", () => {
|
||||||
|
const root = makeTmpDir()
|
||||||
|
const result = backupDatabase({
|
||||||
|
dbPath: path.join(root, 'inexistant.db'),
|
||||||
|
backupsDir: path.join(root, 'backups')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(fs.existsSync(path.join(root, 'backups'))).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('copie le fichier de base dans le dossier de sauvegardes', () => {
|
||||||
|
const root = makeTmpDir()
|
||||||
|
const dbPath = path.join(root, 'data.db')
|
||||||
|
fs.writeFileSync(dbPath, 'contenu-de-la-base')
|
||||||
|
const backupsDir = path.join(root, 'backups')
|
||||||
|
|
||||||
|
const result = backupDatabase({dbPath, backupsDir})
|
||||||
|
|
||||||
|
expect(result).not.toBeNull()
|
||||||
|
expect(fs.existsSync(result)).toBe(true)
|
||||||
|
expect(fs.readFileSync(result, 'utf8')).toBe('contenu-de-la-base')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('ne conserve que les 8 sauvegardes les plus récentes', () => {
|
||||||
|
const root = makeTmpDir()
|
||||||
|
const dbPath = path.join(root, 'data.db')
|
||||||
|
fs.writeFileSync(dbPath, 'contenu')
|
||||||
|
const backupsDir = path.join(root, 'backups')
|
||||||
|
fs.mkdirSync(backupsDir, {recursive: true})
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
fs.writeFileSync(path.join(backupsDir, `data-2020-01-0${i}.db`), 'ancien')
|
||||||
|
}
|
||||||
|
|
||||||
|
backupDatabase({dbPath, backupsDir})
|
||||||
|
|
||||||
|
const remaining = fs.readdirSync(backupsDir).filter(name => name.startsWith('data-'))
|
||||||
|
expect(remaining).toHaveLength(8)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const RETENTION = 8;
|
||||||
|
|
||||||
|
function backupDatabase({dbPath, backupsDir}) {
|
||||||
|
if (!fs.existsSync(dbPath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(backupsDir, {recursive: true});
|
||||||
|
|
||||||
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
|
const backupPath = path.join(backupsDir, `data-${timestamp}.db`);
|
||||||
|
fs.copyFileSync(dbPath, backupPath);
|
||||||
|
|
||||||
|
const backups = fs.readdirSync(backupsDir)
|
||||||
|
.filter(name => name.startsWith('data-') && name.endsWith('.db'))
|
||||||
|
.sort();
|
||||||
|
|
||||||
|
const toDelete = backups.slice(0, Math.max(backups.length - RETENTION, 0));
|
||||||
|
for (const name of toDelete) {
|
||||||
|
fs.unlinkSync(path.join(backupsDir, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return backupPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {backupDatabase};
|
||||||
Reference in New Issue
Block a user