improved typesafety

This commit is contained in:
Daniel 2023-05-27 19:59:39 +03:30
parent a0a7ccc952
commit 36778810c5
11 changed files with 87 additions and 89 deletions

View File

@ -3,7 +3,7 @@
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. // You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
import { ExtendedLink } from "@/types/global"; import { LinkIncludingCollectionAndTags } from "@/types/global";
import { import {
faFolder, faFolder,
faArrowUpRightFromSquare, faArrowUpRightFromSquare,
@ -25,7 +25,7 @@ export default function ({
link, link,
count, count,
}: { }: {
link: ExtendedLink; link: LinkIncludingCollectionAndTags;
count: number; count: number;
}) { }) {
const [expandDropdown, setExpandDropdown] = useState(false); const [expandDropdown, setExpandDropdown] = useState(false);
@ -34,11 +34,14 @@ export default function ({
const { removeLink } = useLinkStore(); const { removeLink } = useLinkStore();
const url = new URL(link.url); const url = new URL(link.url);
const formattedDate = new Date(link.createdAt).toLocaleString("en-US", { const formattedDate = new Date(link.createdAt as string).toLocaleString(
year: "numeric", "en-US",
month: "short", {
day: "numeric", year: "numeric",
}); month: "short",
day: "numeric",
}
);
const toggleEditModal = () => { const toggleEditModal = () => {
setEditModal(!editModal); setEditModal(!editModal);

View File

@ -14,8 +14,8 @@ type Props = {
onChange: any; onChange: any;
defaultValue: defaultValue:
| { | {
value: number;
label: string; label: string;
value?: number;
} }
| undefined; | undefined;
}; };

View File

@ -4,6 +4,6 @@
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. // You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
export interface Options { export interface Options {
value: string | number;
label: string; label: string;
value?: string | number;
} }

View File

@ -3,7 +3,7 @@
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. // You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
import { ExtendedLink } from "@/types/global"; import { LinkIncludingCollectionAndTags } from "@/types/global";
import { import {
faFolder, faFolder,
faArrowUpRightFromSquare, faArrowUpRightFromSquare,
@ -25,7 +25,7 @@ export default function ({
link, link,
count, count,
}: { }: {
link: ExtendedLink; link: LinkIncludingCollectionAndTags;
count: number; count: number;
}) { }) {
const [expandDropdown, setExpandDropdown] = useState(false); const [expandDropdown, setExpandDropdown] = useState(false);
@ -34,11 +34,14 @@ export default function ({
const { removeLink } = useLinkStore(); const { removeLink } = useLinkStore();
const url = new URL(link.url); const url = new URL(link.url);
const formattedDate = new Date(link.createdAt).toLocaleString("en-US", { const formattedDate = new Date(link.createdAt as string).toLocaleString(
year: "numeric", "en-US",
month: "short", {
day: "numeric", year: "numeric",
}); month: "short",
day: "numeric",
}
);
const toggleEditModal = () => { const toggleEditModal = () => {
setEditModal(!editModal); setEditModal(!editModal);

View File

@ -8,7 +8,7 @@ import CollectionSelection from "@/components/InputSelect/CollectionSelection";
import TagSelection from "@/components/InputSelect/TagSelection"; import TagSelection from "@/components/InputSelect/TagSelection";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { ExtendedLink, NewLink } from "@/types/global"; import { LinkIncludingCollectionAndTags } from "@/types/global";
import useLinkStore from "@/store/links"; import useLinkStore from "@/store/links";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useCollectionStore from "@/store/collections"; import useCollectionStore from "@/store/collections";
@ -20,14 +20,17 @@ type Props = {
export default function AddLink({ toggleLinkModal }: Props) { export default function AddLink({ toggleLinkModal }: Props) {
const router = useRouter(); const router = useRouter();
const [newLink, setNewLink] = useState<NewLink>({ const [link, setLink] = useState<LinkIncludingCollectionAndTags>({
name: "", name: "",
url: "", url: "",
title: "",
screenshotPath: "",
pdfPath: "",
tags: [], tags: [],
collection: { collection: {
id: undefined,
name: "", name: "",
ownerId: undefined, description: "",
ownerId: 1,
}, },
}); });
@ -40,14 +43,15 @@ export default function AddLink({ toggleLinkModal }: Props) {
(e) => e.id == Number(router.query.id) (e) => e.id == Number(router.query.id)
); );
setNewLink({ if (currentCollection)
...newLink, setLink({
collection: { ...link,
id: currentCollection?.id, collection: {
name: currentCollection?.name, id: currentCollection?.id,
ownerId: currentCollection?.ownerId, name: currentCollection?.name,
}, ownerId: currentCollection?.ownerId,
}); },
});
} }
}, []); }, []);
@ -56,22 +60,22 @@ export default function AddLink({ toggleLinkModal }: Props) {
return { name: e.label }; return { name: e.label };
}); });
setNewLink({ ...newLink, tags: tagNames }); setLink({ ...link, tags: tagNames });
}; };
const setCollection = (e: any) => { const setCollection = (e: any) => {
if (e?.__isNew__) e.value = null; if (e?.__isNew__) e.value = null;
setNewLink({ setLink({
...newLink, ...link,
collection: { id: e?.value, name: e?.label, ownerId: e?.ownerId }, collection: { id: e?.value, name: e?.label, ownerId: e?.ownerId },
}); });
}; };
const submit = async () => { const submit = async () => {
console.log(newLink); console.log(link);
const response = await addLink(newLink as NewLink); const response = await addLink(link);
if (response) toggleLinkModal(); if (response) toggleLinkModal();
}; };
@ -87,8 +91,8 @@ export default function AddLink({ toggleLinkModal }: Props) {
<RequiredBadge /> <RequiredBadge />
</p> </p>
<input <input
value={newLink.name} value={link.name}
onChange={(e) => setNewLink({ ...newLink, name: e.target.value })} onChange={(e) => setLink({ ...link, name: e.target.value })}
type="text" type="text"
placeholder="e.g. Example Link" placeholder="e.g. Example Link"
className="w-full rounded-md p-2 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" className="w-full rounded-md p-2 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
@ -101,8 +105,8 @@ export default function AddLink({ toggleLinkModal }: Props) {
<RequiredBadge /> <RequiredBadge />
</p> </p>
<input <input
value={newLink.url} value={link.url}
onChange={(e) => setNewLink({ ...newLink, url: e.target.value })} onChange={(e) => setLink({ ...link, url: e.target.value })}
type="text" type="text"
placeholder="e.g. http://example.com/" placeholder="e.g. http://example.com/"
className="w-full rounded-md p-2 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" className="w-full rounded-md p-2 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
@ -116,10 +120,10 @@ export default function AddLink({ toggleLinkModal }: Props) {
</p> </p>
<CollectionSelection <CollectionSelection
defaultValue={ defaultValue={
newLink.collection.name && newLink.collection.id link.collection.name && link.collection.id
? { ? {
value: newLink.collection.id, value: link.collection.id,
label: newLink.collection.name, label: link.collection.name,
} }
: undefined : undefined
} }

View File

@ -7,7 +7,7 @@ import React, { useState } from "react";
import CollectionSelection from "@/components/InputSelect/CollectionSelection"; import CollectionSelection from "@/components/InputSelect/CollectionSelection";
import TagSelection from "@/components/InputSelect/TagSelection"; import TagSelection from "@/components/InputSelect/TagSelection";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ExtendedLink } from "@/types/global"; import { LinkIncludingCollectionAndTags } from "@/types/global";
import { faPenToSquare } from "@fortawesome/free-regular-svg-icons"; import { faPenToSquare } from "@fortawesome/free-regular-svg-icons";
import useLinkStore from "@/store/links"; import useLinkStore from "@/store/links";
import { faTrashCan } from "@fortawesome/free-solid-svg-icons"; import { faTrashCan } from "@fortawesome/free-solid-svg-icons";
@ -15,11 +15,12 @@ import RequiredBadge from "../RequiredBadge";
type Props = { type Props = {
toggleLinkModal: Function; toggleLinkModal: Function;
link: ExtendedLink; link: LinkIncludingCollectionAndTags;
}; };
export default function EditLink({ toggleLinkModal, link }: Props) { export default function EditLink({ toggleLinkModal, link }: Props) {
const [currentLink, setCurrentLink] = useState<ExtendedLink>(link); const [currentLink, setCurrentLink] =
useState<LinkIncludingCollectionAndTags>(link);
const { updateLink, removeLink } = useLinkStore(); const { updateLink, removeLink } = useLinkStore();

View File

@ -4,13 +4,17 @@
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. // You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
import { prisma } from "@/lib/api/db"; import { prisma } from "@/lib/api/db";
import { ExtendedLink } from "@/types/global"; import { LinkIncludingCollectionAndTags } from "@/types/global";
import fs from "fs"; import fs from "fs";
import { Link, UsersAndCollections } from "@prisma/client"; import { Link, UsersAndCollections } from "@prisma/client";
import getPermission from "@/lib/api/getPermission"; import getPermission from "@/lib/api/getPermission";
export default async function (link: ExtendedLink, userId: number) { export default async function (
if (!link) return { response: "Please choose a valid link.", status: 401 }; link: LinkIncludingCollectionAndTags,
userId: number
) {
if (!link || !link.collectionId)
return { response: "Please choose a valid link.", status: 401 };
const collectionIsAccessible = await getPermission(userId, link.collectionId); const collectionIsAccessible = await getPermission(userId, link.collectionId);

View File

@ -4,7 +4,7 @@
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. // You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
import { prisma } from "@/lib/api/db"; import { prisma } from "@/lib/api/db";
import { ExtendedLink } from "@/types/global"; import { LinkIncludingCollectionAndTags } from "@/types/global";
import getTitle from "../../getTitle"; import getTitle from "../../getTitle";
import archive from "../../archive"; import archive from "../../archive";
import { Link, UsersAndCollections } from "@prisma/client"; import { Link, UsersAndCollections } from "@prisma/client";
@ -12,7 +12,10 @@ import AES from "crypto-js/aes";
import getPermission from "@/lib/api/getPermission"; import getPermission from "@/lib/api/getPermission";
import { existsSync, mkdirSync } from "fs"; import { existsSync, mkdirSync } from "fs";
export default async function (link: ExtendedLink, userId: number) { export default async function (
link: LinkIncludingCollectionAndTags,
userId: number
) {
link.collection.name = link.collection.name.trim(); link.collection.name = link.collection.name.trim();
if (!link.name) { if (!link.name) {
@ -21,7 +24,7 @@ export default async function (link: ExtendedLink, userId: number) {
return { response: "Please enter a valid collection name.", status: 401 }; return { response: "Please enter a valid collection name.", status: 401 };
} }
if (link.collection.ownerId) { if (link.collection.id) {
const collectionIsAccessible = await getPermission( const collectionIsAccessible = await getPermission(
userId, userId,
link.collection.id link.collection.id
@ -81,8 +84,6 @@ export default async function (link: ExtendedLink, userId: number) {
}, },
}); });
console.log(newLink);
const AES_SECRET = process.env.AES_SECRET as string; const AES_SECRET = process.env.AES_SECRET as string;
const screenshotHashedPath = AES.encrypt( const screenshotHashedPath = AES.encrypt(
@ -95,7 +96,7 @@ export default async function (link: ExtendedLink, userId: number) {
AES_SECRET AES_SECRET
).toString(); ).toString();
const updatedLink: ExtendedLink = await prisma.link.update({ const updatedLink = await prisma.link.update({
where: { id: newLink.id }, where: { id: newLink.id },
data: { screenshotPath: screenshotHashedPath, pdfPath: pdfHashedPath }, data: { screenshotPath: screenshotHashedPath, pdfPath: pdfHashedPath },
include: { tags: true, collection: true }, include: { tags: true, collection: true },
@ -105,8 +106,6 @@ export default async function (link: ExtendedLink, userId: number) {
if (!existsSync(collectionPath)) if (!existsSync(collectionPath))
mkdirSync(collectionPath, { recursive: true }); mkdirSync(collectionPath, { recursive: true });
console.log(updatedLink);
archive(updatedLink.url, updatedLink.collectionId, updatedLink.id); archive(updatedLink.url, updatedLink.collectionId, updatedLink.id);
return { response: updatedLink, status: 200 }; return { response: updatedLink, status: 200 };

View File

@ -4,14 +4,17 @@
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. // You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
import { prisma } from "@/lib/api/db"; import { prisma } from "@/lib/api/db";
import { ExtendedLink } from "@/types/global"; import { LinkIncludingCollectionAndTags } from "@/types/global";
import { UsersAndCollections } from "@prisma/client"; import { UsersAndCollections } from "@prisma/client";
import getPermission from "@/lib/api/getPermission"; import getPermission from "@/lib/api/getPermission";
export default async function (link: ExtendedLink, userId: number) { export default async function (
link: LinkIncludingCollectionAndTags,
userId: number
) {
if (!link) return { response: "Please choose a valid link.", status: 401 }; if (!link) return { response: "Please choose a valid link.", status: 401 };
if (link.collection.ownerId) { if (link.collection.id) {
const collectionIsAccessible = await getPermission( const collectionIsAccessible = await getPermission(
userId, userId,
link.collection.id link.collection.id
@ -27,7 +30,7 @@ export default async function (link: ExtendedLink, userId: number) {
link.collection.ownerId = userId; link.collection.ownerId = userId;
} }
const updatedLink: ExtendedLink = await prisma.link.update({ const updatedLink = await prisma.link.update({
where: { where: {
id: link.id, id: link.id,
}, },

View File

@ -4,16 +4,16 @@
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. // You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
import { create } from "zustand"; import { create } from "zustand";
import { ExtendedLink, NewLink } from "@/types/global"; import { LinkIncludingCollectionAndTags } from "@/types/global";
import useTagStore from "./tags"; import useTagStore from "./tags";
import useCollectionStore from "./collections"; import useCollectionStore from "./collections";
type LinkStore = { type LinkStore = {
links: ExtendedLink[]; links: LinkIncludingCollectionAndTags[];
setLinks: () => void; setLinks: () => void;
addLink: (body: NewLink) => Promise<boolean>; addLink: (body: LinkIncludingCollectionAndTags) => Promise<boolean>;
updateLink: (link: ExtendedLink) => void; updateLink: (link: LinkIncludingCollectionAndTags) => void;
removeLink: (link: ExtendedLink) => void; removeLink: (link: LinkIncludingCollectionAndTags) => void;
}; };
const useLinkStore = create<LinkStore>()((set) => ({ const useLinkStore = create<LinkStore>()((set) => ({

View File

@ -5,35 +5,16 @@
import { Collection, Link, Tag, User } from "@prisma/client"; import { Collection, Link, Tag, User } from "@prisma/client";
type OptionalExcluding<T, TRequired extends keyof T> = Partial<T> & export type OptionalExcluding<T, TRequired extends keyof T> = Partial<T> &
Pick<T, TRequired>; Pick<T, TRequired>;
export interface ExtendedLink extends Link { export interface LinkIncludingCollectionAndTags
extends Omit<Link, "id" | "createdAt" | "collectionId"> {
id?: number;
createdAt?: string;
collectionId?: number;
tags: Tag[]; tags: Tag[];
collection: Collection; collection: OptionalExcluding<Collection, "name" | "ownerId">;
}
export interface NewLink {
name: string;
url: string;
tags: Tag[];
collection: {
id: number | undefined;
name: string | undefined;
ownerId: number | undefined;
};
}
export interface NewCollection {
name: string;
description: string;
members: {
name: string;
email: string;
canCreate: boolean;
canUpdate: boolean;
canDelete: boolean;
}[];
} }
export interface Member { export interface Member {