From c974cc558847cdfda509051e2a648b8c6d6a9b89 Mon Sep 17 00:00:00 2001 From: Psykka <36780964+Psykka@users.noreply.github.com> Date: Sat, 22 Jun 2024 17:45:44 -0300 Subject: [PATCH] feat: pull db from discord --- .env.example | 5 ++- package.json | 6 ++- pnpm-lock.yaml | 52 +++++++++++++++++++++++ scripts/pull_db | 110 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 scripts/pull_db diff --git a/.env.example b/.env.example index 30705a8..5be1889 100644 --- a/.env.example +++ b/.env.example @@ -14,4 +14,7 @@ DEPLOY_WEBHOOK_TOKEN=your_token_here DEPLOY_WEBHOOK_ID=your_id_here # discord backup webhook url -DISCORD_BACKUP_WEBHOOK=your_webhook_here \ No newline at end of file +DISCORD_BACKUP_WEBHOOK=your_webhook_here + +# discord backup channel id +BACKUP_CHANNEL_ID=your_channel_id_here \ No newline at end of file diff --git a/package.json b/package.json index ef7cde0..40f7a35 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "lint": "biome check src", "lint:fix": "biome check --apply src", "generate": "prisma generate", - "prepare": "husky || true" + "prepare": "husky || true", + "backup:pull": "node --env-file=.env scripts/pull_db" }, "devDependencies": { "@biomejs/biome": "1.7.3", @@ -26,6 +27,7 @@ "husky": "^9.0.11", "nodemon": "^3.1.1", "rimraf": "^5.0.7", + "tar": "^7.4.0", "tsc-alias": "^1.8.10", "typescript": "^5.4.5" }, @@ -39,4 +41,4 @@ "discord.js": "14.x", "prisma": "^5.14.0" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40744b8..9aa895f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,6 +54,9 @@ importers: rimraf: specifier: ^5.0.7 version: 5.0.7 + tar: + specifier: ^7.4.0 + version: 7.4.0 tsc-alias: specifier: ^1.8.10 version: 1.8.10 @@ -148,6 +151,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@mole-inc/bin-wrapper@8.0.1': resolution: {integrity: sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -434,6 +441,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + clone-response@1.0.3: resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} @@ -747,6 +758,15 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@3.0.1: + resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} + engines: {node: '>= 18'} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -993,6 +1013,10 @@ packages: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} + tar@7.4.0: + resolution: {integrity: sha512-XQs0S8fuAkQWuqhDeCdMlJXDX80D7EOVLDPVFkna9yQfzS+PHKgfxcei0jf6/+QAWcjqrnC8uM3fSAnrQl+XYg==} + engines: {node: '>=18'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1072,6 +1096,10 @@ packages: yallist@2.1.2: resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + snapshots: '@biomejs/biome@1.7.3': @@ -1165,6 +1193,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + '@mole-inc/bin-wrapper@8.0.1': dependencies: bin-check: 4.1.0 @@ -1450,6 +1482,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chownr@3.0.0: {} + clone-response@1.0.3: dependencies: mimic-response: 1.0.1 @@ -1760,6 +1794,13 @@ snapshots: minipass@7.1.2: {} + minizlib@3.0.1: + dependencies: + minipass: 7.1.2 + rimraf: 5.0.7 + + mkdirp@3.0.1: {} + ms@2.1.2: {} mylas@2.1.13: {} @@ -1972,6 +2013,15 @@ snapshots: dependencies: has-flag: 3.0.0 + tar@7.4.0: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.1 + mkdirp: 3.0.1 + yallist: 5.0.0 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -2035,3 +2085,5 @@ snapshots: ws@8.17.0: {} yallist@2.1.2: {} + + yallist@5.0.0: {} diff --git a/scripts/pull_db b/scripts/pull_db new file mode 100644 index 0000000..b353e71 --- /dev/null +++ b/scripts/pull_db @@ -0,0 +1,110 @@ +#!/usr/bin/env node + +import * as tar from 'tar'; +import { createWriteStream, mkdir, unlink, existsSync } from 'node:fs'; +import { pipeline } from 'stream'; +import { promisify } from 'node:util'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { tmpdir } from 'node:os'; + +const streamPipeline = promisify(pipeline); +const tempPath = tmpdir(); +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const BACKUP_CHANNEL_ID = process.env.BACKUP_CHANNEL_ID; +const pathToExtract = `${process.cwd()}/prisma`; +const fileName = 'db.sqlite' + +const throwError = (message) => { + console.error(message); + process.exit(1); +} + +console.log('Pulling backup from Discord...'); + +const fileUrl = await fetch(`https://discord.com/api/v10/channels/${BACKUP_CHANNEL_ID}/messages`, { + headers: { + Authorization: `Bot ${process.env.TOKEN}`, + }, +}) + .then((res) => { + if (!res.ok) { + throw new Error(`Failed to fetch backup URL: ${res.statusText}`); + } + + return res.json(); + }) + .then((data) => { + const message = data.find((message) => message.attachments.length > 0); + if (!message) { + throw new Error('No backup URL found'); + } + + const attachment = message.attachments[0] + if (!attachment) { + throw new Error('No backup URL found'); + } + + return attachment.url; + }) + .catch((err) => { + throwError(`Failed to fetch backup URL: ${err}`); + }); + +if (!fileUrl) { + throwError('No backup URL found'); +} + +const tarballPath = resolve(tempPath, 'backup.tar.gz'); +const extractPath = resolve(__dirname, pathToExtract); +const jornalPath = resolve(extractPath, `${fileName}-jornal`); + +console.log(`Downloading backup from:\n${fileUrl.split('?')[0]}\n`); + +await fetch(fileUrl) + .then((res) => { + if (!res.ok) { + throw new Error(`Failed to download backup: ${res.statusText}`); + } + + return res.body + }) + .then((body) => { + const writeStream = createWriteStream(tarballPath); + return streamPipeline(body, writeStream); + }) + .catch((err) => { + throwError(`Failed to download backup: ${err}`); + }); + +console.log(`Extracting backup to:\n${extractPath}\n`); + +await mkdir(extractPath, { recursive: true }, (err) => { + if (err) { + throwError(`Failed to create backup directory: ${err}`); + } +}); + +await tar.extract({ + file: tarballPath, + cwd: extractPath, + strip: 1, + filter: (path) => path.endsWith(fileName), +}) + +await unlink(tarballPath, (err) => { + if (err) { + throwError(`Failed to delete tarball: ${err}`); + } +}); + +if (existsSync(jornalPath)) { + await unlink(jornalPath, (err) => { + if (err) { + throwError(`Failed to delete jornal file: ${err}`); + } + }); +} + +console.log('Backup extracted successfully!\n'); \ No newline at end of file