improved typesafety
This commit is contained in:
parent
a0a7ccc952
commit
36778810c5
|
@ -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(
|
||||||
|
"en-US",
|
||||||
|
{
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const toggleEditModal = () => {
|
const toggleEditModal = () => {
|
||||||
setEditModal(!editModal);
|
setEditModal(!editModal);
|
||||||
|
|
|
@ -14,8 +14,8 @@ type Props = {
|
||||||
onChange: any;
|
onChange: any;
|
||||||
defaultValue:
|
defaultValue:
|
||||||
| {
|
| {
|
||||||
value: number;
|
|
||||||
label: string;
|
label: string;
|
||||||
|
value?: number;
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
"en-US",
|
||||||
|
{
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const toggleEditModal = () => {
|
const toggleEditModal = () => {
|
||||||
setEditModal(!editModal);
|
setEditModal(!editModal);
|
||||||
|
|
|
@ -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,8 +43,9 @@ 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({
|
||||||
|
...link,
|
||||||
collection: {
|
collection: {
|
||||||
id: currentCollection?.id,
|
id: currentCollection?.id,
|
||||||
name: currentCollection?.name,
|
name: currentCollection?.name,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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) => ({
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Ŝarĝante…
Reference in New Issue