Merge branch 'feat/handle-files' into dev
This commit is contained in:
commit
104c79cd99
|
@ -16,7 +16,7 @@ import useAccountStore from "@/store/account";
|
||||||
import { faCalendarDays } from "@fortawesome/free-regular-svg-icons";
|
import { faCalendarDays } from "@fortawesome/free-regular-svg-icons";
|
||||||
import usePermissions from "@/hooks/usePermissions";
|
import usePermissions from "@/hooks/usePermissions";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import isValidUrl from "@/lib/client/isValidUrl";
|
import isValidUrl from "@/lib/shared/isValidUrl";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import unescapeString from "@/lib/client/unescapeString";
|
import unescapeString from "@/lib/client/unescapeString";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
@ -43,7 +43,7 @@ export default function LinkCard({ link, count, className }: Props) {
|
||||||
let shortendURL;
|
let shortendURL;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
shortendURL = new URL(link.url).host.toLowerCase();
|
shortendURL = new URL(link.url || "").host.toLowerCase();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,8 @@ export default function LinkCard({ link, count, className }: Props) {
|
||||||
response.ok && toast.success(`Link Deleted.`);
|
response.ok && toast.success(`Link Deleted.`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = isValidUrl(link.url) ? new URL(link.url) : undefined;
|
const url =
|
||||||
|
isValidUrl(link.url || "") && link.url ? new URL(link.url) : undefined;
|
||||||
|
|
||||||
const formattedDate = new Date(link.createdAt as string).toLocaleString(
|
const formattedDate = new Date(link.createdAt as string).toLocaleString(
|
||||||
"en-US",
|
"en-US",
|
||||||
|
@ -272,7 +273,7 @@ export default function LinkCard({ link, count, className }: Props) {
|
||||||
) : undefined} */}
|
) : undefined} */}
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href={link.url}
|
href={link.url || ""}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { faFolder, faLink } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { faCalendarDays } from "@fortawesome/free-regular-svg-icons";
|
import { faCalendarDays } from "@fortawesome/free-regular-svg-icons";
|
||||||
import isValidUrl from "@/lib/client/isValidUrl";
|
import isValidUrl from "@/lib/shared/isValidUrl";
|
||||||
import A from "next/link";
|
import A from "next/link";
|
||||||
import unescapeString from "@/lib/client/unescapeString";
|
import unescapeString from "@/lib/client/unescapeString";
|
||||||
import { Link } from "@prisma/client";
|
import { Link } from "@prisma/client";
|
||||||
|
|
|
@ -44,6 +44,7 @@ export default function AddOrEditLink({
|
||||||
activeLink || {
|
activeLink || {
|
||||||
name: "",
|
name: "",
|
||||||
url: "",
|
url: "",
|
||||||
|
type: "",
|
||||||
description: "",
|
description: "",
|
||||||
tags: [],
|
tags: [],
|
||||||
screenshotPath: "",
|
screenshotPath: "",
|
||||||
|
@ -139,10 +140,10 @@ export default function AddOrEditLink({
|
||||||
{method === "UPDATE" ? (
|
{method === "UPDATE" ? (
|
||||||
<div
|
<div
|
||||||
className="text-neutral break-all w-full flex gap-2"
|
className="text-neutral break-all w-full flex gap-2"
|
||||||
title={link.url}
|
title={link.url || ""}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faLink} className="w-6 h-6" />
|
<FontAwesomeIcon icon={faLink} className="w-6 h-6" />
|
||||||
<Link href={link.url} target="_blank" className="w-full">
|
<Link href={link.url || ""} target="_blank" className="w-full">
|
||||||
{link.url}
|
{link.url}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -153,7 +154,7 @@ export default function AddOrEditLink({
|
||||||
<div className="sm:col-span-3 col-span-5">
|
<div className="sm:col-span-3 col-span-5">
|
||||||
<p className="mb-2">Address (URL)</p>
|
<p className="mb-2">Address (URL)</p>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={link.url}
|
value={link.url || ""}
|
||||||
onChange={(e) => setLink({ ...link, url: e.target.value })}
|
onChange={(e) => setLink({ ...link, url: e.target.value })}
|
||||||
placeholder="e.g. http://example.com/"
|
placeholder="e.g. http://example.com/"
|
||||||
className="bg-base-200"
|
className="bg-base-200"
|
||||||
|
|
|
@ -76,8 +76,7 @@ export default function PreservedFormats() {
|
||||||
// Create a temporary link and click it to trigger the download
|
// Create a temporary link and click it to trigger the download
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = path;
|
link.href = path;
|
||||||
link.download =
|
link.download = format === ArchivedFormat.png ? "Screenshot" : "PDF";
|
||||||
format === ArchivedFormat.screenshot ? "Screenshot" : "PDF";
|
|
||||||
link.click();
|
link.click();
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to download file");
|
console.error("Failed to download file");
|
||||||
|
@ -102,7 +101,7 @@ export default function PreservedFormats() {
|
||||||
|
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<div
|
<div
|
||||||
onClick={() => handleDownload(ArchivedFormat.screenshot)}
|
onClick={() => handleDownload(ArchivedFormat.png)}
|
||||||
className="cursor-pointer hover:opacity-60 duration-100 p-2 rounded-md"
|
className="cursor-pointer hover:opacity-60 duration-100 p-2 rounded-md"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
|
@ -112,7 +111,11 @@ export default function PreservedFormats() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href={`/api/v1/archives/${link?.id}?format=${ArchivedFormat.screenshot}`}
|
href={`/api/v1/archives/${link?.id}?format=${
|
||||||
|
link.screenshotPath.endsWith("png")
|
||||||
|
? ArchivedFormat.png
|
||||||
|
: ArchivedFormat.jpeg
|
||||||
|
}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="cursor-pointer hover:opacity-60 duration-100 p-2 rounded-md"
|
className="cursor-pointer hover:opacity-60 duration-100 p-2 rounded-md"
|
||||||
>
|
>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Link as LinkType, Tag } from "@prisma/client";
|
import { Link as LinkType, Tag } from "@prisma/client";
|
||||||
import isValidUrl from "@/lib/client/isValidUrl";
|
import isValidUrl from "@/lib/shared/isValidUrl";
|
||||||
import unescapeString from "@/lib/client/unescapeString";
|
import unescapeString from "@/lib/client/unescapeString";
|
||||||
import { TagIncludingLinkCount } from "@/types/global";
|
import { TagIncludingLinkCount } from "@/types/global";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
@ -17,7 +17,7 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LinkCard({ link, count }: Props) {
|
export default function LinkCard({ link, count }: Props) {
|
||||||
const url = isValidUrl(link.url) ? new URL(link.url) : undefined;
|
const url = link.url && isValidUrl(link.url) ? new URL(link.url) : undefined;
|
||||||
|
|
||||||
const formattedDate = new Date(
|
const formattedDate = new Date(
|
||||||
link.createdAt as unknown as string
|
link.createdAt as unknown as string
|
||||||
|
@ -68,10 +68,10 @@ export default function LinkCard({ link, count }: Props) {
|
||||||
<p>{formattedDate}</p>
|
<p>{formattedDate}</p>
|
||||||
<p>·</p>
|
<p>·</p>
|
||||||
<Link
|
<Link
|
||||||
href={url ? url.href : link.url}
|
href={url ? url.href : link.url || ""}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="hover:opacity-50 duration-100 truncate w-52 sm:w-fit"
|
className="hover:opacity-50 duration-100 truncate w-52 sm:w-fit"
|
||||||
title={url ? url.href : link.url}
|
title={url ? url.href : link.url || ""}
|
||||||
>
|
>
|
||||||
{url ? url.host : link.url}
|
{url ? url.host : link.url}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
import { prisma } from "@/lib/api/db";
|
import { prisma } from "@/lib/api/db";
|
||||||
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
|
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
|
||||||
import getTitle from "@/lib/api/getTitle";
|
import getTitle from "@/lib/api/getTitle";
|
||||||
import archive from "@/lib/api/archive";
|
import urlHandler from "@/lib/api/urlHandler";
|
||||||
import { UsersAndCollections } from "@prisma/client";
|
import { UsersAndCollections } from "@prisma/client";
|
||||||
import getPermission from "@/lib/api/getPermission";
|
import getPermission from "@/lib/api/getPermission";
|
||||||
import createFolder from "@/lib/api/storage/createFolder";
|
import createFolder from "@/lib/api/storage/createFolder";
|
||||||
|
import pdfHandler from "../../pdfHandler";
|
||||||
|
import validateUrlSize from "../../validateUrlSize";
|
||||||
|
import imageHandler from "../../imageHandler";
|
||||||
|
|
||||||
export default async function postLink(
|
export default async function postLink(
|
||||||
link: LinkIncludingShortenedCollectionAndTags,
|
link: LinkIncludingShortenedCollectionAndTags,
|
||||||
userId: number
|
userId: number
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
new URL(link.url);
|
if (link.url) new URL(link.url);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
response:
|
response:
|
||||||
|
@ -45,13 +48,33 @@ export default async function postLink(
|
||||||
const description =
|
const description =
|
||||||
link.description && link.description !== ""
|
link.description && link.description !== ""
|
||||||
? link.description
|
? link.description
|
||||||
: await getTitle(link.url);
|
: link.url
|
||||||
|
? await getTitle(link.url)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const validatedUrl = link.url ? await validateUrlSize(link.url) : undefined;
|
||||||
|
|
||||||
|
if (validatedUrl === null)
|
||||||
|
return { response: "File is too large to be stored.", status: 400 };
|
||||||
|
|
||||||
|
const contentType = validatedUrl?.get("content-type");
|
||||||
|
let linkType = "url";
|
||||||
|
let imageExtension = "png";
|
||||||
|
|
||||||
|
if (!link.url) linkType = link.type;
|
||||||
|
else if (contentType === "application/pdf") linkType = "pdf";
|
||||||
|
else if (contentType?.startsWith("image")) {
|
||||||
|
linkType = "image";
|
||||||
|
if (contentType === "image/jpeg") imageExtension = "jpeg";
|
||||||
|
else if (contentType === "image/png") imageExtension = "png";
|
||||||
|
}
|
||||||
|
|
||||||
const newLink = await prisma.link.create({
|
const newLink = await prisma.link.create({
|
||||||
data: {
|
data: {
|
||||||
url: link.url,
|
url: link.url,
|
||||||
name: link.name,
|
name: link.name,
|
||||||
description,
|
description,
|
||||||
|
type: linkType,
|
||||||
readabilityPath: "pending",
|
readabilityPath: "pending",
|
||||||
collection: {
|
collection: {
|
||||||
connectOrCreate: {
|
connectOrCreate: {
|
||||||
|
@ -91,7 +114,15 @@ export default async function postLink(
|
||||||
|
|
||||||
createFolder({ filePath: `archives/${newLink.collectionId}` });
|
createFolder({ filePath: `archives/${newLink.collectionId}` });
|
||||||
|
|
||||||
archive(newLink.id, newLink.url, userId);
|
newLink.url && linkType === "url"
|
||||||
|
? urlHandler(newLink.id, newLink.url, userId)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
linkType === "pdf" ? pdfHandler(newLink.id, newLink.url) : undefined;
|
||||||
|
|
||||||
|
linkType === "image"
|
||||||
|
? imageHandler(newLink.id, newLink.url, imageExtension)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return { response: newLink, status: 200 };
|
return { response: newLink, status: 200 };
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { prisma } from "@/lib/api/db";
|
||||||
|
import createFile from "@/lib/api/storage/createFile";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export default async function imageHandler(
|
||||||
|
linkId: number,
|
||||||
|
url: string | null,
|
||||||
|
extension: string,
|
||||||
|
file?: string
|
||||||
|
) {
|
||||||
|
const pdf = await fetch(url as string).then((res) => res.blob());
|
||||||
|
|
||||||
|
const buffer = Buffer.from(await pdf.arrayBuffer());
|
||||||
|
|
||||||
|
const linkExists = await prisma.link.findUnique({
|
||||||
|
where: { id: linkId },
|
||||||
|
});
|
||||||
|
|
||||||
|
linkExists
|
||||||
|
? await createFile({
|
||||||
|
data: buffer,
|
||||||
|
filePath: `archives/${linkExists.collectionId}/${linkId}.${extension}`,
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
await prisma.link.update({
|
||||||
|
where: { id: linkId },
|
||||||
|
data: {
|
||||||
|
screenshotPath: linkExists
|
||||||
|
? `archives/${linkExists.collectionId}/${linkId}.${extension}`
|
||||||
|
: null,
|
||||||
|
pdfPath: null,
|
||||||
|
readabilityPath: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { prisma } from "@/lib/api/db";
|
||||||
|
import createFile from "@/lib/api/storage/createFile";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export default async function pdfHandler(
|
||||||
|
linkId: number,
|
||||||
|
url: string | null,
|
||||||
|
file?: string
|
||||||
|
) {
|
||||||
|
const targetLink = await prisma.link.update({
|
||||||
|
where: { id: linkId },
|
||||||
|
data: {
|
||||||
|
pdfPath: "pending",
|
||||||
|
lastPreserved: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const pdf = await fetch(url as string).then((res) => res.blob());
|
||||||
|
|
||||||
|
const buffer = Buffer.from(await pdf.arrayBuffer());
|
||||||
|
|
||||||
|
const linkExists = await prisma.link.findUnique({
|
||||||
|
where: { id: linkId },
|
||||||
|
});
|
||||||
|
|
||||||
|
linkExists
|
||||||
|
? await createFile({
|
||||||
|
data: buffer,
|
||||||
|
filePath: `archives/${linkExists.collectionId}/${linkId}.pdf`,
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
await prisma.link.update({
|
||||||
|
where: { id: linkId },
|
||||||
|
data: {
|
||||||
|
pdfPath: linkExists
|
||||||
|
? `archives/${linkExists.collectionId}/${linkId}.pdf`
|
||||||
|
: null,
|
||||||
|
readabilityPath: null,
|
||||||
|
screenshotPath: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
|
@ -97,7 +97,7 @@ export default async function readFile(filePath: string) {
|
||||||
return {
|
return {
|
||||||
file: "File not found.",
|
file: "File not found.",
|
||||||
contentType: "text/plain",
|
contentType: "text/plain",
|
||||||
status: 400,
|
status: 404,
|
||||||
};
|
};
|
||||||
else {
|
else {
|
||||||
const file = fs.readFileSync(creationPath);
|
const file = fs.readFileSync(creationPath);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Readability } from "@mozilla/readability";
|
||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
|
|
||||||
export default async function archive(
|
export default async function urlHandler(
|
||||||
linkId: number,
|
linkId: number,
|
||||||
url: string,
|
url: string,
|
||||||
userId: number
|
userId: number
|
|
@ -0,0 +1,13 @@
|
||||||
|
export default async function validateUrlSize(url: string) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { method: "HEAD" });
|
||||||
|
|
||||||
|
const totalSizeMB =
|
||||||
|
Number(response.headers.get("content-length")) / Math.pow(1024, 2);
|
||||||
|
if (totalSizeMB > 50) return null;
|
||||||
|
else return response.headers;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,8 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
|
||||||
let suffix;
|
let suffix;
|
||||||
|
|
||||||
if (format === ArchivedFormat.screenshot) suffix = ".png";
|
if (format === ArchivedFormat.png) suffix = ".png";
|
||||||
|
else if (format === ArchivedFormat.jpeg) suffix = ".jpeg";
|
||||||
else if (format === ArchivedFormat.pdf) suffix = ".pdf";
|
else if (format === ArchivedFormat.pdf) suffix = ".pdf";
|
||||||
else if (format === ArchivedFormat.readability) suffix = "_readability.json";
|
else if (format === ArchivedFormat.readability) suffix = "_readability.json";
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const { file, contentType, status } = await readFile(
|
const { file, contentType, status } = await readFile(
|
||||||
`archives/${collectionIsAccessible.id}/${linkId + suffix}`
|
`archives/${collectionIsAccessible.id}/${linkId + suffix}`
|
||||||
);
|
);
|
||||||
|
|
||||||
res.setHeader("Content-Type", contentType).status(status as number);
|
res.setHeader("Content-Type", contentType).status(status as number);
|
||||||
|
|
||||||
return res.send(file);
|
return res.send(file);
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import archive from "@/lib/api/archive";
|
import urlHandler from "@/lib/api/urlHandler";
|
||||||
import { prisma } from "@/lib/api/db";
|
import { prisma } from "@/lib/api/db";
|
||||||
import verifyUser from "@/lib/api/verifyUser";
|
import verifyUser from "@/lib/api/verifyUser";
|
||||||
|
import isValidUrl from "@/lib/shared/isValidUrl";
|
||||||
|
|
||||||
const RE_ARCHIVE_LIMIT = Number(process.env.RE_ARCHIVE_LIMIT) || 5;
|
const RE_ARCHIVE_LIMIT = Number(process.env.RE_ARCHIVE_LIMIT) || 5;
|
||||||
|
|
||||||
|
@ -41,7 +42,13 @@ export default async function links(req: NextApiRequest, res: NextApiResponse) {
|
||||||
} minutes or create a new one.`,
|
} minutes or create a new one.`,
|
||||||
});
|
});
|
||||||
|
|
||||||
archive(link.id, link.url, user.id);
|
if (link.url && isValidUrl(link.url)) {
|
||||||
|
urlHandler(link.id, link.url, user.id);
|
||||||
|
return res.status(200).json({
|
||||||
|
response: "Link is not a webpage to be archived.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
response: "Link is being archived.",
|
response: "Link is being archived.",
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import ColorThief, { RGBColor } from "colorthief";
|
import ColorThief, { RGBColor } from "colorthief";
|
||||||
import unescapeString from "@/lib/client/unescapeString";
|
import unescapeString from "@/lib/client/unescapeString";
|
||||||
import isValidUrl from "@/lib/client/isValidUrl";
|
import isValidUrl from "@/lib/shared/isValidUrl";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faBoxesStacked, faFolder } from "@fortawesome/free-solid-svg-icons";
|
import { faBoxesStacked, faFolder } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import ColorThief, { RGBColor } from "colorthief";
|
import ColorThief, { RGBColor } from "colorthief";
|
||||||
import unescapeString from "@/lib/client/unescapeString";
|
import unescapeString from "@/lib/client/unescapeString";
|
||||||
import isValidUrl from "@/lib/client/isValidUrl";
|
import isValidUrl from "@/lib/shared/isValidUrl";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faBoxesStacked, faFolder } from "@fortawesome/free-solid-svg-icons";
|
import { faBoxesStacked, faFolder } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Link" ADD COLUMN "type" TEXT NOT NULL DEFAULT 'url',
|
||||||
|
ALTER COLUMN "url" DROP NOT NULL;
|
|
@ -103,12 +103,13 @@ model UsersAndCollections {
|
||||||
model Link {
|
model Link {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String
|
name String
|
||||||
url String
|
type String @default("url")
|
||||||
description String @default("")
|
description String @default("")
|
||||||
pinnedBy User[]
|
pinnedBy User[]
|
||||||
collection Collection @relation(fields: [collectionId], references: [id])
|
collection Collection @relation(fields: [collectionId], references: [id])
|
||||||
collectionId Int
|
collectionId Int
|
||||||
tags Tag[]
|
tags Tag[]
|
||||||
|
url String?
|
||||||
textContent String?
|
textContent String?
|
||||||
screenshotPath String?
|
screenshotPath String?
|
||||||
pdfPath String?
|
pdfPath String?
|
||||||
|
|
|
@ -117,7 +117,14 @@ export type DeleteUserBody = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum ArchivedFormat {
|
export enum ArchivedFormat {
|
||||||
screenshot,
|
png,
|
||||||
|
jpeg,
|
||||||
pdf,
|
pdf,
|
||||||
readability,
|
readability,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum LinkType {
|
||||||
|
url,
|
||||||
|
pdf,
|
||||||
|
image,
|
||||||
|
}
|
||||||
|
|
Ŝarĝante…
Reference in New Issue