diff --git a/__tests__/backup.test.js b/__tests__/backup.test.js new file mode 100644 index 0000000..c420ffa --- /dev/null +++ b/__tests__/backup.test.js @@ -0,0 +1,84 @@ +const path = require("path"); +const { default: Prisma } = require("../db/dbconprisma"); + +require("dotenv").config({ + path: path.join(__dirname, "../", ".env"), +}); + +const { + restoreBackupData, + getPageDatawLinkAndSocialData, +} = require("../lib/dbfuncprisma"); +describe("Test backup function", () => { + beforeAll(async () => { + await Prisma.pagedata.deleteMany(); + await Prisma.socialdata.deleteMany(); + await Prisma.linkdata.deleteMany(); + await Prisma.pagedata.create({ + data: { + id: 1, + handlerText: "test", + avatarUrl: "", + avatarBorderColor: "#ffffff", + bgColor: "#7ea2ff", + accentColor: "#bdd7ff", + handlerFontSize: "20", + handlerFontColor: "#ffffff", + avatarwidth: "50", + footerBgColor: "#7ea2ff", + footerTextSize: "12", + footerText: "Test footer", + footerTextColor: "#ffffff", + handlerDescription: "Test description", + handlerDescriptionFontColor: "#ffffff", + linktreeWidth: "320", + linkdata: { + create: { + bgColor: "#2C6BED", + textColor: "#ffffff", + displayText: "Welcome to Linkin", + iconClass: "fas fa-link", + linkUrl: "https://github.com/RizkyRajitha/linkin", + }, + }, + socialdata: { + create: { + iconClass: "fab fa-github", + linkUrl: "https://github.com/RizkyRajitha/linkin", + bgColor: "#2C6BED", + borderRadius: "5", + }, + }, + }, + }); + }); + + beforeEach(() => { + jest.spyOn(console, "log").mockImplementation(() => {}); + }); + + afterAll(async () => { + await Prisma.$disconnect(); + }); + + test("fails when no data provided", async () => { + await expect(restoreBackupData).rejects.toThrow("no data to backup"); + }); + + test("backups correctly with correct info", async () => { + const dataToBackup = { + pageData: { + id: 1, + handlerText: "Test Backup", + }, + socialData: [], + linkData: [], + }; + await restoreBackupData(dataToBackup); + let dbInfo = await getPageDatawLinkAndSocialData(); + + for (let n in dataToBackup.pageData) { + expect(dataToBackup.pageData[n]).toBe(dbInfo.pageData[n]); + } + }); +}); diff --git a/components/backup.js b/components/backup.js new file mode 100644 index 0000000..91bb345 --- /dev/null +++ b/components/backup.js @@ -0,0 +1,115 @@ +import { toast } from "react-toastify"; +import Swal from "sweetalert2"; + +const BackupComponent = () => { + const endpoint = + process.env.NODE_ENV === "production" ? `` : "http://localhost:3000"; + + const getBackup = async () => { + try { + let res = await fetch(`${endpoint}/api/user/backup`).then((res) => + res.json() + ); + let data = JSON.stringify(res, undefined, 2); + let blob = new Blob([data], { type: "application/json" }); + + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.style.display = "none"; + a.href = url; + + // filename = linkin-backup-date.json + let filename = `linkin-backup-${new Date() + .toISOString() + .slice(0, 16) + .replace(":", "-")}.json`; + a.download = filename; // set filename + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); // avoid memory leaks + } catch (error) { + toast.error(`${error.message}`, { autoClose: 10000 }); + } + }; + + const handleBackup = async (event) => { + event.preventDefault(); + + let file = document.getElementById("backup").files[0]; + if (!file) return toast.error(`No file selected`, { autoClose: 5000 }); + + let confirm = await Swal.fire({ + title: "Restore Data", + text: "You won't be able to revert this!", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Restore data", + }); + + if (!confirm.isConfirmed) return; + + let fr = new FileReader(); + + fr.onload = async function () { + const dataJson = JSON.parse(fr.result); + + await fetch(`${endpoint}/api/user/backup`, { + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + method: "POST", + body: JSON.stringify(dataJson), + }) + .then(async (res) => { + console.log(res); + if (!res.ok) throw Error((await res.json()).message); + location.reload(); + }) // reload for getting restored page data + .catch((err) => { + toast.error(`${err.message}`, { autoClose: 10000 }); + toast.error(`File may be corrupted`, { autoClose: 10000 }); + }); + }; + + fr.readAsText(file); + }; + + return ( + <> +