From 62594050455049744458c9658a73ba48ae4d9b9e Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Mon, 16 Oct 2023 18:27:04 -0400 Subject: [PATCH] added export functionality --- .../Modal/Collection/TeamManagement.tsx | 4 +- components/Modal/User/PrivacySettings.tsx | 47 ++++++++-- components/SettingsSidebar.tsx | 20 ++++ lib/api/controllers/migration/exportData.ts | 13 +++ .../{importData.ts => importFromHTMLFile.ts} | 5 +- .../migration/importFromLinkwarden.ts | 91 +++++++++++++++++++ pages/api/migration/index.ts | 16 +++- pages/settings/migration.tsx | 10 ++ types/global.ts | 10 ++ 9 files changed, 202 insertions(+), 14 deletions(-) rename lib/api/controllers/migration/{importData.ts => importFromHTMLFile.ts} (96%) create mode 100644 lib/api/controllers/migration/importFromLinkwarden.ts create mode 100644 pages/settings/migration.tsx diff --git a/components/Modal/Collection/TeamManagement.tsx b/components/Modal/Collection/TeamManagement.tsx index 2904517..633e0b9 100644 --- a/components/Modal/Collection/TeamManagement.tsx +++ b/components/Modal/Collection/TeamManagement.tsx @@ -156,7 +156,9 @@ export default function TeamManagement({ ) : null} - {permissions !== true && collection.isPublic &&
} + {permissions !== true && collection.isPublic && ( +
+ )} {permissions === true && ( <> diff --git a/components/Modal/User/PrivacySettings.tsx b/components/Modal/User/PrivacySettings.tsx index b068363..df28907 100644 --- a/components/Modal/User/PrivacySettings.tsx +++ b/components/Modal/User/PrivacySettings.tsx @@ -1,7 +1,11 @@ import { Dispatch, SetStateAction, useEffect, useState } from "react"; import Checkbox from "../../Checkbox"; import useAccountStore from "@/store/account"; -import { AccountSettings } from "@/types/global"; +import { + AccountSettings, + MigrationFormat, + MigrationRequest, +} from "@/types/global"; import { signOut, useSession } from "next-auth/react"; import { faPenToSquare } from "@fortawesome/free-regular-svg-icons"; import SubmitButton from "../../SubmitButton"; @@ -50,7 +54,7 @@ export default function PrivacySettings({ return wordsArray; }; - const postBookmarkFile = async (e: any) => { + const importBookmarks = async (e: any, format: MigrationFormat) => { const file: File = e.target.files[0]; if (file) { @@ -59,9 +63,16 @@ export default function PrivacySettings({ reader.onload = async function (e) { const load = toast.loading("Importing..."); + const request: string = e.target?.result as string; + + const body: MigrationRequest = { + format, + data: request, + }; + const response = await fetch("/api/migration", { method: "POST", - body: e.target?.result, + body: JSON.stringify(body), }); const data = await response.json(); @@ -180,7 +191,7 @@ export default function PrivacySettings({ >
+
@@ -199,12 +229,11 @@ export default function PrivacySettings({ ) : null} - {/* Commented out for now. */} - {/* +
Export Data
- */} + diff --git a/components/SettingsSidebar.tsx b/components/SettingsSidebar.tsx index eb26f78..3852b2e 100644 --- a/components/SettingsSidebar.tsx +++ b/components/SettingsSidebar.tsx @@ -6,6 +6,7 @@ import { faBoxArchive, faLock, faKey, + faRightLeft, } from "@fortawesome/free-solid-svg-icons"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -93,6 +94,25 @@ export default function SettingsSidebar({ className }: { className?: string }) { + +
+ + +

+ Import & Export +

+
+ +
redactIds(o)); + } else if (obj !== null && typeof obj === "object") { + delete obj.id; + for (let key in obj) { + redactIds(obj[key]); + } + } + } + + redactIds(userData); + return { response: userData, status: 200 }; } diff --git a/lib/api/controllers/migration/importData.ts b/lib/api/controllers/migration/importFromHTMLFile.ts similarity index 96% rename from lib/api/controllers/migration/importData.ts rename to lib/api/controllers/migration/importFromHTMLFile.ts index c7bc33e..36c48c0 100644 --- a/lib/api/controllers/migration/importData.ts +++ b/lib/api/controllers/migration/importFromHTMLFile.ts @@ -3,7 +3,10 @@ import { Backup } from "@/types/global"; import createFolder from "@/lib/api/storage/createFolder"; import { JSDOM } from "jsdom"; -export default async function importData(userId: number, rawData: string) { +export default async function importFromHTMLFile( + userId: number, + rawData: string +) { try { const dom = new JSDOM(rawData); const document = dom.window.document; diff --git a/lib/api/controllers/migration/importFromLinkwarden.ts b/lib/api/controllers/migration/importFromLinkwarden.ts new file mode 100644 index 0000000..eb68e7e --- /dev/null +++ b/lib/api/controllers/migration/importFromLinkwarden.ts @@ -0,0 +1,91 @@ +import { prisma } from "@/lib/api/db"; +import { Backup } from "@/types/global"; +import createFolder from "@/lib/api/storage/createFolder"; + +export default async function getData(userId: number, rawData: string) { + const data: Backup = JSON.parse(rawData); + + console.log(typeof data); + + // Import collections + try { + for (const e of data.collections) { + e.name = e.name.trim(); + + const findCollection = await prisma.user.findUnique({ + where: { + id: userId, + }, + select: { + collections: { + where: { + name: e.name, + }, + }, + }, + }); + + const checkIfCollectionExists = findCollection?.collections[0]; + + let collectionId = findCollection?.collections[0]?.id; + + if (!checkIfCollectionExists) { + const newCollection = await prisma.collection.create({ + data: { + owner: { + connect: { + id: userId, + }, + }, + name: e.name, + description: e.description, + color: e.color, + }, + }); + + createFolder({ filePath: `archives/${newCollection.id}` }); + + collectionId = newCollection.id; + } + + // Import Links + for (const link of e.links) { + const newLink = await prisma.link.create({ + data: { + url: link.url, + name: link.name, + description: link.description, + collection: { + connect: { + id: collectionId, + }, + }, + // Import Tags + tags: { + connectOrCreate: link.tags.map((tag) => ({ + where: { + name_ownerId: { + name: tag.name.trim(), + ownerId: userId, + }, + }, + create: { + name: tag.name.trim(), + owner: { + connect: { + id: userId, + }, + }, + }, + })), + }, + }, + }); + } + } + } catch (err) { + console.log(err); + } + + return { response: "Success.", status: 200 }; +} diff --git a/pages/api/migration/index.ts b/pages/api/migration/index.ts index c082c3d..aeeb499 100644 --- a/pages/api/migration/index.ts +++ b/pages/api/migration/index.ts @@ -2,7 +2,9 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { getServerSession } from "next-auth/next"; import { authOptions } from "@/pages/api/auth/[...nextauth]"; import exportData from "@/lib/api/controllers/migration/exportData"; -import importData from "@/lib/api/controllers/migration/importData"; +import importFromHTMLFile from "@/lib/api/controllers/migration/importFromHTMLFile"; +import importFromLinkwarden from "@/lib/api/controllers/migration/importFromLinkwarden"; +import { MigrationFormat, MigrationRequest } from "@/types/global"; export default async function users(req: NextApiRequest, res: NextApiResponse) { const session = await getServerSession(req, res, authOptions); @@ -25,7 +27,15 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) { .status(data.status) .json(data.response); } else if (req.method === "POST") { - const data = await importData(session.user.id, req.body); - return res.status(data.status).json({ response: data.response }); + const request: MigrationRequest = JSON.parse(req.body); + + let data; + if (request.format === MigrationFormat.htmlFile) + data = await importFromHTMLFile(session.user.id, request.data); + + if (request.format === MigrationFormat.linkwarden) + data = await importFromLinkwarden(session.user.id, request.data); + + if (data) return res.status(data.status).json({ response: data.response }); } } diff --git a/pages/settings/migration.tsx b/pages/settings/migration.tsx new file mode 100644 index 0000000..023c2ca --- /dev/null +++ b/pages/settings/migration.tsx @@ -0,0 +1,10 @@ +import SettingsLayout from "@/layouts/SettingsLayout"; +import React from "react"; + +export default function appearance() { + return ( + +
Migration
+
+ ); +} diff --git a/types/global.ts b/types/global.ts index f52f771..a80bacb 100644 --- a/types/global.ts +++ b/types/global.ts @@ -86,6 +86,16 @@ export interface Backup extends Omit { collections: CollectionIncludingLinks[]; } +export type MigrationRequest = { + format: MigrationFormat; + data: string; +}; + +export enum MigrationFormat { + linkwarden, + htmlFile, +} + export enum Plan { monthly, yearly,