Labels truly half (or less) working
This commit is contained in:
parent
50a89d4104
commit
53ca8614b8
10
apps/web/app/dashboard/labels/page.tsx
Normal file
10
apps/web/app/dashboard/labels/page.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from "react";
|
||||||
|
import LabelsView from "@/components/dashboard/labels/LabelsView";
|
||||||
|
|
||||||
|
export default async function LabelsPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<LabelsView />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,15 +0,0 @@
|
|||||||
import AllTagsView from "@/components/dashboard/tags/AllTagsView";
|
|
||||||
import { Separator } from "@/components/ui/separator";
|
|
||||||
import { api } from "@/server/api/client";
|
|
||||||
|
|
||||||
export default async function TagsPage() {
|
|
||||||
const allTags = (await api.tags.list()).tags;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-4 rounded-md border bg-background p-4">
|
|
||||||
<span className="text-2xl">All Tags</span>
|
|
||||||
<Separator />
|
|
||||||
<AllTagsView initialData={allTags} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -4,7 +4,7 @@ import { getServerAuthSession } from "@/server/auth";
|
|||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
const session = await getServerAuthSession();
|
const session = await getServerAuthSession();
|
||||||
if (session) {
|
if (session) {
|
||||||
redirect("/dashboard/settings");
|
redirect("/dashboard/today");
|
||||||
} else {
|
} else {
|
||||||
redirect("/signin");
|
redirect("/signin");
|
||||||
}
|
}
|
||||||
|
|||||||
156
apps/web/components/dashboard/labels/AddLabelDialog.tsx
Normal file
156
apps/web/components/dashboard/labels/AddLabelDialog.tsx
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { ActionButton } from "@/components/ui/action-button";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { toast } from "@/components/ui/use-toast";
|
||||||
|
import { api } from "@/lib/trpc";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { TRPCClientError } from "@trpc/client";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { HexColorPicker } from "react-colorful";
|
||||||
|
import { zLabelSchema } from "@lifetracker/shared/types/labels";
|
||||||
|
|
||||||
|
type CreateLabelSchema = z.infer<typeof zLabelSchema>;
|
||||||
|
|
||||||
|
export default function AddLabelDialog({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const apiUtils = api.useUtils();
|
||||||
|
const [isOpen, onOpenChange] = useState(false);
|
||||||
|
const form = useForm<CreateLabelSchema>({
|
||||||
|
resolver: zodResolver(zLabelSchema),
|
||||||
|
defaultValues: {
|
||||||
|
code: -1,
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
color: "#000022",
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
const { mutate, isPending } = api.labels.createLabel.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
toast({
|
||||||
|
description: "Label created successfully",
|
||||||
|
});
|
||||||
|
onOpenChange(false);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
if (error instanceof TRPCClientError) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
description: error.message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
description: "Failed to create user",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
}, [isOpen, form]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||||
|
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>New Label</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit((data) => mutate(data))}>
|
||||||
|
<div className="flex w-full flex-col space-y-2">
|
||||||
|
|
||||||
|
<div className="flex">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Name</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Name"
|
||||||
|
{...field}
|
||||||
|
className="w-full rounded border p-2"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Description</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Description"
|
||||||
|
{...field}
|
||||||
|
className="w-full rounded border p-2"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<DialogFooter className="sm:justify-end">
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button type="button" variant="secondary">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
<ActionButton
|
||||||
|
type="submit"
|
||||||
|
loading={isPending}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</ActionButton>
|
||||||
|
</DialogFooter>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog >
|
||||||
|
);
|
||||||
|
}
|
||||||
84
apps/web/components/dashboard/labels/LabelsView.tsx
Normal file
84
apps/web/components/dashboard/labels/LabelsView.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ActionButtonWithTooltip } from "@/components/ui/action-button";
|
||||||
|
import { ButtonWithTooltip } from "@/components/ui/button";
|
||||||
|
import LoadingSpinner from "@/components/ui/spinner";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { toast } from "@/components/ui/use-toast";
|
||||||
|
import { api } from "@/lib/trpc";
|
||||||
|
import { Check, KeyRound, Pencil, Trash, FilePlus, X } from "lucide-react";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
import AddLabelDialog from "./AddLabelDialog";
|
||||||
|
|
||||||
|
export default function LabelsView() {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
const { data: labels } = api.labels.list.useQuery();
|
||||||
|
|
||||||
|
const LabelsTable = ({ labels }) => (
|
||||||
|
<Table>
|
||||||
|
<TableHeader className="bg-gray-200">
|
||||||
|
<TableHead>Code</TableHead>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Description</TableHead>
|
||||||
|
<TableHead>Entries With Label</TableHead>
|
||||||
|
<TableHead>Actions</TableHead>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{labels.labels.map((l) => (
|
||||||
|
<TableRow key={l.id}>
|
||||||
|
<TableCell className="py-1">{l.code}</TableCell>
|
||||||
|
<TableCell className="py-1">{l.name}</TableCell>
|
||||||
|
<TableCell className="py-1">{l.description}</TableCell>
|
||||||
|
<TableCell className="py-1">
|
||||||
|
{labelStats[l.id].numEntries}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="flex gap-1 py-1">
|
||||||
|
<ActionButtonWithTooltip
|
||||||
|
tooltip="Delete label"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => deleteLabel({ labelId: l.id })}
|
||||||
|
loading={false}
|
||||||
|
>
|
||||||
|
<Trash size={16} color="red" />
|
||||||
|
</ActionButtonWithTooltip>
|
||||||
|
<EditLabelDialog labelId={l.id} >
|
||||||
|
<ButtonWithTooltip
|
||||||
|
tooltip="Edit"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
<Pencil size={16} color="red" />
|
||||||
|
</ButtonWithTooltip>
|
||||||
|
</EditLabelDialog>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-2 flex items-center justify-between text-xl font-medium">
|
||||||
|
<span>All Labels</span>
|
||||||
|
<AddLabelDialog>
|
||||||
|
<ButtonWithTooltip tooltip="Create Label" variant="outline">
|
||||||
|
<FilePlus size={16} />
|
||||||
|
</ButtonWithTooltip>
|
||||||
|
</AddLabelDialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{labels === undefined ? (
|
||||||
|
<LoadingSpinner />
|
||||||
|
) : (
|
||||||
|
<LabelsTable labels={labels} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -39,14 +39,9 @@ export default async function Sidebar() {
|
|||||||
},
|
},
|
||||||
...searchItem,
|
...searchItem,
|
||||||
{
|
{
|
||||||
name: "Tags",
|
name: "Labels",
|
||||||
icon: <Tag size={18} />,
|
icon: <Tag size={18} />,
|
||||||
path: "/dashboard/tags",
|
path: "/dashboard/labels",
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Archive",
|
|
||||||
icon: <Archive size={18} />,
|
|
||||||
path: "/dashboard/archive",
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,210 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { ActionButton } from "@/components/ui/action-button";
|
|
||||||
import ActionConfirmingDialog from "@/components/ui/action-confirming-dialog";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger,
|
|
||||||
} from "@/components/ui/collapsible";
|
|
||||||
import InfoTooltip from "@/components/ui/info-tooltip";
|
|
||||||
import { Separator } from "@/components/ui/separator";
|
|
||||||
import { Toggle } from "@/components/ui/toggle";
|
|
||||||
import { toast } from "@/components/ui/use-toast";
|
|
||||||
import { api } from "@/lib/trpc";
|
|
||||||
import { ArrowDownAZ, Combine } from "lucide-react";
|
|
||||||
|
|
||||||
import type { ZGetTagResponse } from "@hoarder/shared/types/tags";
|
|
||||||
import { useDeleteUnusedTags } from "@hoarder/shared-react/hooks/tags";
|
|
||||||
|
|
||||||
import DeleteTagConfirmationDialog from "./DeleteTagConfirmationDialog";
|
|
||||||
import { TagPill } from "./TagPill";
|
|
||||||
|
|
||||||
function DeleteAllUnusedTags({ numUnusedTags }: { numUnusedTags: number }) {
|
|
||||||
const { mutate, isPending } = useDeleteUnusedTags({
|
|
||||||
onSuccess: () => {
|
|
||||||
toast({
|
|
||||||
description: `Deleted all ${numUnusedTags} unused tags`,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
toast({
|
|
||||||
description: "Something went wrong",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<ActionConfirmingDialog
|
|
||||||
title="Delete all unused tags?"
|
|
||||||
description={`Are you sure you want to delete the ${numUnusedTags} unused tags?`}
|
|
||||||
actionButton={() => (
|
|
||||||
<ActionButton
|
|
||||||
variant="destructive"
|
|
||||||
loading={isPending}
|
|
||||||
onClick={() => mutate()}
|
|
||||||
>
|
|
||||||
DELETE THEM ALL
|
|
||||||
</ActionButton>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Button variant="destructive" disabled={numUnusedTags == 0}>
|
|
||||||
Delete All Unused Tags
|
|
||||||
</Button>
|
|
||||||
</ActionConfirmingDialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const byUsageSorter = (a: ZGetTagResponse, b: ZGetTagResponse) => {
|
|
||||||
// Sort by name if the usage is the same to get a stable result
|
|
||||||
if (b.numBookmarks == a.numBookmarks) {
|
|
||||||
return byNameSorter(a, b);
|
|
||||||
}
|
|
||||||
return b.numBookmarks - a.numBookmarks;
|
|
||||||
};
|
|
||||||
const byNameSorter = (a: ZGetTagResponse, b: ZGetTagResponse) =>
|
|
||||||
a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
|
|
||||||
|
|
||||||
export default function AllTagsView({
|
|
||||||
initialData,
|
|
||||||
}: {
|
|
||||||
initialData: ZGetTagResponse[];
|
|
||||||
}) {
|
|
||||||
interface Tag {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [draggingEnabled, setDraggingEnabled] = React.useState(false);
|
|
||||||
const [sortByName, setSortByName] = React.useState(false);
|
|
||||||
|
|
||||||
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
|
|
||||||
const [selectedTag, setSelectedTag] = React.useState<Tag | null>(null);
|
|
||||||
|
|
||||||
const handleOpenDialog = (tag: Tag) => {
|
|
||||||
setSelectedTag(tag);
|
|
||||||
setIsDialogOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
function toggleSortByName(): void {
|
|
||||||
setSortByName(!sortByName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleDraggingEnabled(): void {
|
|
||||||
setDraggingEnabled(!draggingEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = api.tags.list.useQuery(undefined, {
|
|
||||||
initialData: { tags: initialData },
|
|
||||||
});
|
|
||||||
// Sort tags by usage desc
|
|
||||||
const allTags = data.tags.sort(sortByName ? byNameSorter : byUsageSorter);
|
|
||||||
|
|
||||||
const humanTags = allTags.filter(
|
|
||||||
(t) => (t.numBookmarksByAttachedType.human ?? 0) > 0,
|
|
||||||
);
|
|
||||||
const aiTags = allTags.filter(
|
|
||||||
(t) => (t.numBookmarksByAttachedType.ai ?? 0) > 0,
|
|
||||||
);
|
|
||||||
const emptyTags = allTags.filter((t) => t.numBookmarks === 0);
|
|
||||||
|
|
||||||
const tagsToPill = (tags: typeof allTags) => {
|
|
||||||
let tagPill;
|
|
||||||
if (tags.length) {
|
|
||||||
tagPill = (
|
|
||||||
<div className="flex flex-wrap gap-3">
|
|
||||||
{tags.map((t) => (
|
|
||||||
<TagPill
|
|
||||||
key={t.id}
|
|
||||||
id={t.id}
|
|
||||||
name={t.name}
|
|
||||||
count={t.numBookmarks}
|
|
||||||
isDraggable={draggingEnabled}
|
|
||||||
onOpenDialog={handleOpenDialog}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
tagPill = "No Tags";
|
|
||||||
}
|
|
||||||
return tagPill;
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{selectedTag && (
|
|
||||||
<DeleteTagConfirmationDialog
|
|
||||||
tag={selectedTag}
|
|
||||||
open={isDialogOpen}
|
|
||||||
setOpen={(o) => {
|
|
||||||
if (!o) {
|
|
||||||
setSelectedTag(null);
|
|
||||||
}
|
|
||||||
setIsDialogOpen(o);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="flex justify-end gap-x-2">
|
|
||||||
<Toggle
|
|
||||||
variant="outline"
|
|
||||||
aria-label="Toggle bold"
|
|
||||||
pressed={draggingEnabled}
|
|
||||||
onPressedChange={toggleDraggingEnabled}
|
|
||||||
>
|
|
||||||
<Combine className="mr-2 size-4" />
|
|
||||||
Drag & Drop Merging
|
|
||||||
<InfoTooltip size={15} className="my-auto ml-2" variant="explain">
|
|
||||||
<p>Drag and drop tags on each other to merge them</p>
|
|
||||||
</InfoTooltip>
|
|
||||||
</Toggle>
|
|
||||||
<Toggle
|
|
||||||
variant="outline"
|
|
||||||
aria-label="Toggle bold"
|
|
||||||
pressed={sortByName}
|
|
||||||
onPressedChange={toggleSortByName}
|
|
||||||
>
|
|
||||||
<ArrowDownAZ className="mr-2 size-4" /> Sort by Name
|
|
||||||
</Toggle>
|
|
||||||
</div>
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
<p className="text-lg">Your Tags</p>
|
|
||||||
<InfoTooltip size={15} className="my-auto" variant="explain">
|
|
||||||
<p>Tags that were attached at least once by you</p>
|
|
||||||
</InfoTooltip>
|
|
||||||
</span>
|
|
||||||
{tagsToPill(humanTags)}
|
|
||||||
<Separator />
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
<p className="text-lg">AI Tags</p>
|
|
||||||
<InfoTooltip size={15} className="my-auto" variant="explain">
|
|
||||||
<p>Tags that were only attached automatically (by AI)</p>
|
|
||||||
</InfoTooltip>
|
|
||||||
</span>
|
|
||||||
{tagsToPill(aiTags)}
|
|
||||||
<Separator />
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
<p className="text-lg">Unused Tags</p>
|
|
||||||
<InfoTooltip size={15} className="my-auto" variant="explain">
|
|
||||||
<p>Tags that are not attached to any bookmarks</p>
|
|
||||||
</InfoTooltip>
|
|
||||||
</span>
|
|
||||||
<Collapsible>
|
|
||||||
<div className="space-x-1 pb-2">
|
|
||||||
<CollapsibleTrigger asChild>
|
|
||||||
<Button variant="secondary" disabled={emptyTags.length == 0}>
|
|
||||||
{emptyTags.length > 0
|
|
||||||
? `Show ${emptyTags.length} unused tags`
|
|
||||||
: "You don't have any unused tags"}
|
|
||||||
</Button>
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
{emptyTags.length > 0 && (
|
|
||||||
<DeleteAllUnusedTags numUnusedTags={emptyTags.length} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<CollapsibleContent>{tagsToPill(emptyTags)}</CollapsibleContent>
|
|
||||||
</Collapsible>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -18,11 +18,11 @@
|
|||||||
"@auth/drizzle-adapter": "^1.4.2",
|
"@auth/drizzle-adapter": "^1.4.2",
|
||||||
"@emoji-mart/data": "^1.1.2",
|
"@emoji-mart/data": "^1.1.2",
|
||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
|
"@hookform/resolvers": "^3.3.4",
|
||||||
"@lifetracker/db": "workspace:^",
|
"@lifetracker/db": "workspace:^",
|
||||||
"@lifetracker/shared": "workspace:^0.1.0",
|
"@lifetracker/shared": "workspace:^0.1.0",
|
||||||
"@lifetracker/shared-react": "workspace:^0.1.0",
|
"@lifetracker/shared-react": "workspace:^0.1.0",
|
||||||
"@lifetracker/trpc": "workspace:^",
|
"@lifetracker/trpc": "workspace:^",
|
||||||
"@hookform/resolvers": "^3.3.4",
|
|
||||||
"@radix-ui/react-collapsible": "^1.0.3",
|
"@radix-ui/react-collapsible": "^1.0.3",
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
@ -58,6 +58,7 @@
|
|||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-colorful": "^5.6.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-draggable": "^4.4.6",
|
"react-draggable": "^4.4.6",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
|
|||||||
11
packages/db/migrations/0005_ambitious_famine.sql
Normal file
11
packages/db/migrations/0005_ambitious_famine.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE `label` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`createdAt` integer NOT NULL,
|
||||||
|
`userId` text NOT NULL,
|
||||||
|
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `labels_name_idx` ON `label` (`name`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `labels_userId_idx` ON `label` (`userId`);--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `label_userId_name_unique` ON `label` (`userId`,`name`);
|
||||||
4
packages/db/migrations/0006_milky_hellion.sql
Normal file
4
packages/db/migrations/0006_milky_hellion.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
DROP INDEX IF EXISTS `labels_name_idx`;--> statement-breakpoint
|
||||||
|
DROP INDEX IF EXISTS `labels_userId_idx`;--> statement-breakpoint
|
||||||
|
ALTER TABLE `label` ADD `description` text;--> statement-breakpoint
|
||||||
|
ALTER TABLE `label` ADD `color` text DEFAULT '#000000';
|
||||||
497
packages/db/migrations/meta/0005_snapshot.json
Normal file
497
packages/db/migrations/meta/0005_snapshot.json
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "06e873b5-ab39-469c-9437-bb1cf4bc60e8",
|
||||||
|
"prevId": "bcdf4680-869f-4b4a-9690-1bd45a196387",
|
||||||
|
"tables": {
|
||||||
|
"account": {
|
||||||
|
"name": "account",
|
||||||
|
"columns": {
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"name": "provider",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"providerAccountId": {
|
||||||
|
"name": "providerAccountId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"refresh_token": {
|
||||||
|
"name": "refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"access_token": {
|
||||||
|
"name": "access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"token_type": {
|
||||||
|
"name": "token_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"name": "scope",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"id_token": {
|
||||||
|
"name": "id_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"session_state": {
|
||||||
|
"name": "session_state",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"account_userId_user_id_fk": {
|
||||||
|
"name": "account_userId_user_id_fk",
|
||||||
|
"tableFrom": "account",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"account_provider_providerAccountId_pk": {
|
||||||
|
"columns": [
|
||||||
|
"provider",
|
||||||
|
"providerAccountId"
|
||||||
|
],
|
||||||
|
"name": "account_provider_providerAccountId_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"apiKey": {
|
||||||
|
"name": "apiKey",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"keyId": {
|
||||||
|
"name": "keyId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"keyHash": {
|
||||||
|
"name": "keyHash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"apiKey_keyId_unique": {
|
||||||
|
"name": "apiKey_keyId_unique",
|
||||||
|
"columns": [
|
||||||
|
"keyId"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"apiKey_name_userId_unique": {
|
||||||
|
"name": "apiKey_name_userId_unique",
|
||||||
|
"columns": [
|
||||||
|
"name",
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"apiKey_userId_user_id_fk": {
|
||||||
|
"name": "apiKey_userId_user_id_fk",
|
||||||
|
"tableFrom": "apiKey",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"name": "day",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"mood": {
|
||||||
|
"name": "mood",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"name": "date",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"name": "comment",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"day_date_unique": {
|
||||||
|
"name": "day_date_unique",
|
||||||
|
"columns": [
|
||||||
|
"date"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"name": "label",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"labels_name_idx": {
|
||||||
|
"name": "labels_name_idx",
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"labels_userId_idx": {
|
||||||
|
"name": "labels_userId_idx",
|
||||||
|
"columns": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"label_userId_name_unique": {
|
||||||
|
"name": "label_userId_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"userId",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"label_userId_user_id_fk": {
|
||||||
|
"name": "label_userId_user_id_fk",
|
||||||
|
"tableFrom": "label",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"session": {
|
||||||
|
"name": "session",
|
||||||
|
"columns": {
|
||||||
|
"sessionToken": {
|
||||||
|
"name": "sessionToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires": {
|
||||||
|
"name": "expires",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"session_userId_user_id_fk": {
|
||||||
|
"name": "session_userId_user_id_fk",
|
||||||
|
"tableFrom": "session",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "user",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"emailVerified": {
|
||||||
|
"name": "emailVerified",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"name": "image",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "password",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"name": "role",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'user'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"user_email_unique": {
|
||||||
|
"name": "user_email_unique",
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"verificationToken": {
|
||||||
|
"name": "verificationToken",
|
||||||
|
"columns": {
|
||||||
|
"identifier": {
|
||||||
|
"name": "identifier",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires": {
|
||||||
|
"name": "expires",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"verificationToken_identifier_token_pk": {
|
||||||
|
"columns": [
|
||||||
|
"identifier",
|
||||||
|
"token"
|
||||||
|
],
|
||||||
|
"name": "verificationToken_identifier_token_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
498
packages/db/migrations/meta/0006_snapshot.json
Normal file
498
packages/db/migrations/meta/0006_snapshot.json
Normal file
@ -0,0 +1,498 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "22c67302-ebcd-4ad9-8393-feea41c17455",
|
||||||
|
"prevId": "06e873b5-ab39-469c-9437-bb1cf4bc60e8",
|
||||||
|
"tables": {
|
||||||
|
"account": {
|
||||||
|
"name": "account",
|
||||||
|
"columns": {
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"name": "provider",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"providerAccountId": {
|
||||||
|
"name": "providerAccountId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"refresh_token": {
|
||||||
|
"name": "refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"access_token": {
|
||||||
|
"name": "access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"token_type": {
|
||||||
|
"name": "token_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"name": "scope",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"id_token": {
|
||||||
|
"name": "id_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"session_state": {
|
||||||
|
"name": "session_state",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"account_userId_user_id_fk": {
|
||||||
|
"name": "account_userId_user_id_fk",
|
||||||
|
"tableFrom": "account",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"account_provider_providerAccountId_pk": {
|
||||||
|
"columns": [
|
||||||
|
"provider",
|
||||||
|
"providerAccountId"
|
||||||
|
],
|
||||||
|
"name": "account_provider_providerAccountId_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"apiKey": {
|
||||||
|
"name": "apiKey",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"keyId": {
|
||||||
|
"name": "keyId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"keyHash": {
|
||||||
|
"name": "keyHash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"apiKey_keyId_unique": {
|
||||||
|
"name": "apiKey_keyId_unique",
|
||||||
|
"columns": [
|
||||||
|
"keyId"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"apiKey_name_userId_unique": {
|
||||||
|
"name": "apiKey_name_userId_unique",
|
||||||
|
"columns": [
|
||||||
|
"name",
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"apiKey_userId_user_id_fk": {
|
||||||
|
"name": "apiKey_userId_user_id_fk",
|
||||||
|
"tableFrom": "apiKey",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"name": "day",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"mood": {
|
||||||
|
"name": "mood",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"name": "date",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"name": "comment",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"day_date_unique": {
|
||||||
|
"name": "day_date_unique",
|
||||||
|
"columns": [
|
||||||
|
"date"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"name": "label",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"name": "color",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'#000000'"
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"label_userId_name_unique": {
|
||||||
|
"name": "label_userId_name_unique",
|
||||||
|
"columns": [
|
||||||
|
"userId",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"label_userId_user_id_fk": {
|
||||||
|
"name": "label_userId_user_id_fk",
|
||||||
|
"tableFrom": "label",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"session": {
|
||||||
|
"name": "session",
|
||||||
|
"columns": {
|
||||||
|
"sessionToken": {
|
||||||
|
"name": "sessionToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires": {
|
||||||
|
"name": "expires",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"session_userId_user_id_fk": {
|
||||||
|
"name": "session_userId_user_id_fk",
|
||||||
|
"tableFrom": "session",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "user",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"emailVerified": {
|
||||||
|
"name": "emailVerified",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"name": "image",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "password",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"name": "role",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'user'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"user_email_unique": {
|
||||||
|
"name": "user_email_unique",
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"verificationToken": {
|
||||||
|
"name": "verificationToken",
|
||||||
|
"columns": {
|
||||||
|
"identifier": {
|
||||||
|
"name": "identifier",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires": {
|
||||||
|
"name": "expires",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"verificationToken_identifier_token_pk": {
|
||||||
|
"columns": [
|
||||||
|
"identifier",
|
||||||
|
"token"
|
||||||
|
],
|
||||||
|
"name": "verificationToken_identifier_token_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -36,6 +36,20 @@
|
|||||||
"when": 1731637559763,
|
"when": 1731637559763,
|
||||||
"tag": "0004_sad_sally_floyd",
|
"tag": "0004_sad_sally_floyd",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 5,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1731648881226,
|
||||||
|
"tag": "0005_ambitious_famine",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 6,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1731649046343,
|
||||||
|
"tag": "0006_milky_hellion",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -105,6 +105,26 @@ export const days = sqliteTable("day", {
|
|||||||
comment: text("comment").notNull(),
|
comment: text("comment").notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const labels = sqliteTable(
|
||||||
|
"label",
|
||||||
|
{
|
||||||
|
id: text("id")
|
||||||
|
.notNull()
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => createId()),
|
||||||
|
createdAt: createdAtField(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
description: text("description"),
|
||||||
|
color: text("color").default("#000000"),
|
||||||
|
userId: text("userId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
},
|
||||||
|
(lb) => ({
|
||||||
|
uniq: unique().on(lb.userId, lb.name)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
export const config = sqliteTable("config", {
|
export const config = sqliteTable("config", {
|
||||||
key: text("key").notNull().primaryKey(),
|
key: text("key").notNull().primaryKey(),
|
||||||
value: text("value").notNull(),
|
value: text("value").notNull(),
|
||||||
@ -118,3 +138,17 @@ export const apiKeyRelations = relations(apiKeys, ({ one }) => ({
|
|||||||
references: [users.id],
|
references: [users.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
export const userRelations = relations(users, ({ many }) => ({
|
||||||
|
labels: many(labels),
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
export const labelsRelations = relations(
|
||||||
|
labels,
|
||||||
|
({ many, one }) => ({
|
||||||
|
user: one(users, {
|
||||||
|
fields: [labels.userId],
|
||||||
|
references: [users.id],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
24
packages/shared/types/labels.ts
Normal file
24
packages/shared/types/labels.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const zLabelSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
code: z.coerce.number(),
|
||||||
|
name: z.string(),
|
||||||
|
color: z.string().default("#000000"),
|
||||||
|
description: z.string().optional(),
|
||||||
|
});
|
||||||
|
export type ZLabels = z.infer<typeof zLabelSchema>;
|
||||||
|
|
||||||
|
export const zGetLabelResponseSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
code: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
numEntries: z.number(),
|
||||||
|
});
|
||||||
|
export type ZGetLabelResponse = z.infer<typeof zGetLabelResponseSchema>;
|
||||||
|
|
||||||
|
export const zUpdateLabelRequestSchema = z.object({
|
||||||
|
labelId: z.string(),
|
||||||
|
code: z.number(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
});
|
||||||
@ -3,11 +3,13 @@ import { router } from "../index";
|
|||||||
import { usersAppRouter } from "./users";
|
import { usersAppRouter } from "./users";
|
||||||
import { apiKeysAppRouter } from "./apiKeys";
|
import { apiKeysAppRouter } from "./apiKeys";
|
||||||
import { adminAppRouter } from "./admin";
|
import { adminAppRouter } from "./admin";
|
||||||
|
import { labelsAppRouter } from "./labels";
|
||||||
|
|
||||||
export const appRouter = router({
|
export const appRouter = router({
|
||||||
users: usersAppRouter,
|
users: usersAppRouter,
|
||||||
apiKeys: apiKeysAppRouter,
|
apiKeys: apiKeysAppRouter,
|
||||||
admin: adminAppRouter,
|
admin: adminAppRouter,
|
||||||
|
labels: labelsAppRouter,
|
||||||
});
|
});
|
||||||
// export type definition of API
|
// export type definition of API
|
||||||
export type AppRouter = typeof appRouter;
|
export type AppRouter = typeof appRouter;
|
||||||
146
packages/trpc/routers/labels.ts
Normal file
146
packages/trpc/routers/labels.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
|
||||||
|
import { and, desc, eq, inArray, notExists } from "drizzle-orm";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SqliteError } from "@lifetracker/db";
|
||||||
|
import { labels } from "@lifetracker/db/schema";
|
||||||
|
import {
|
||||||
|
zLabelSchema,
|
||||||
|
zGetLabelResponseSchema,
|
||||||
|
} from "@lifetracker/shared/types/labels";
|
||||||
|
import type { Context } from "../index";
|
||||||
|
import { authedProcedure, router } from "../index";
|
||||||
|
|
||||||
|
|
||||||
|
function conditionFromInput(input: { labelId: string }, userId: string) {
|
||||||
|
return and(eq(labels.id, input.labelId), eq(labels.userId, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createLabel(
|
||||||
|
input: z.infer<typeof zLabelSchema>,
|
||||||
|
ctx: Context,
|
||||||
|
) {
|
||||||
|
return ctx.db.transaction(async (trx) => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await trx
|
||||||
|
.insert(labels)
|
||||||
|
.values({
|
||||||
|
name: input.name,
|
||||||
|
code: input.code,
|
||||||
|
description: input.description,
|
||||||
|
color: input.color,
|
||||||
|
userId: ctx.user!.id,
|
||||||
|
})
|
||||||
|
.returning({
|
||||||
|
id: labels.id,
|
||||||
|
name: labels.name,
|
||||||
|
code: labels.code,
|
||||||
|
description: labels.description,
|
||||||
|
color: labels.color,
|
||||||
|
});
|
||||||
|
return result[0];
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof SqliteError) {
|
||||||
|
if (e.code == "SQLITE_CONSTRAINT_UNIQUE") {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Line 48 trpc routers labels.ts",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: "Something went wrong",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const labelsAppRouter = router({
|
||||||
|
list: authedProcedure
|
||||||
|
.output(
|
||||||
|
z.object({
|
||||||
|
labels: z.array(zGetLabelResponseSchema),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.query(async ({ ctx }) => {
|
||||||
|
const res = await ctx.db
|
||||||
|
.select({
|
||||||
|
id: labels.id,
|
||||||
|
name: labels.name,
|
||||||
|
color: labels.color,
|
||||||
|
description: labels.description,
|
||||||
|
})
|
||||||
|
.from(labels)
|
||||||
|
.where(eq(labels.userId, ctx.user.id));
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels: res.map((r) => ({
|
||||||
|
id: r.id,
|
||||||
|
name: r.name,
|
||||||
|
color: r.color,
|
||||||
|
description: r.description,
|
||||||
|
numEntries: 420,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
get: authedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
labelId: z.string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.output(zGetLabelResponseSchema)
|
||||||
|
.query(async ({ input, ctx }) => {
|
||||||
|
const res = await ctx.db
|
||||||
|
.select({
|
||||||
|
id: labels.id,
|
||||||
|
name: labels.name
|
||||||
|
})
|
||||||
|
.from(labels)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
conditionFromInput(input, ctx.user.id),
|
||||||
|
eq(labels.userId, ctx.user.id),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.length == 0) {
|
||||||
|
throw new TRPCError({ code: "NOT_FOUND" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const numEntriesWithLabel = res.reduce<
|
||||||
|
Record<ZLabeledByEnum, number>
|
||||||
|
>(
|
||||||
|
(acc, curr) => {
|
||||||
|
if (curr.labeledBy) {
|
||||||
|
acc[curr.labeledBy]++;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ ai: 0, human: 0 },
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: res[0].id,
|
||||||
|
name: res[0].name,
|
||||||
|
numEntries: 420
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
createLabel: authedProcedure
|
||||||
|
.input(zLabelSchema)
|
||||||
|
.output(
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
code: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
color: z.string().default("#000000"),
|
||||||
|
description: z.string().optional(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
console.log("Just started creating a label");
|
||||||
|
return createLabel(input, ctx);
|
||||||
|
}),
|
||||||
|
});
|
||||||
20
pnpm-lock.yaml
generated
20
pnpm-lock.yaml
generated
@ -256,6 +256,9 @@ importers:
|
|||||||
react:
|
react:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.3.1
|
version: 18.3.1
|
||||||
|
react-colorful:
|
||||||
|
specifier: ^5.6.1
|
||||||
|
version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.3.1(react@18.3.1)
|
version: 18.3.1(react@18.3.1)
|
||||||
@ -9707,6 +9710,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
react-colorful@5.6.1:
|
||||||
|
resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
|
||||||
react-dev-utils@12.0.1:
|
react-dev-utils@12.0.1:
|
||||||
resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==}
|
resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@ -19684,7 +19693,7 @@ snapshots:
|
|||||||
eslint: 8.57.0
|
eslint: 8.57.0
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@8.57.0))(eslint@8.57.0)
|
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@8.57.0))(eslint@8.57.0)
|
||||||
eslint-plugin-import: 2.29.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
eslint-plugin-import: 2.29.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
|
||||||
eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0)
|
eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0)
|
||||||
eslint-plugin-react: 7.33.2(eslint@8.57.0)
|
eslint-plugin-react: 7.33.2(eslint@8.57.0)
|
||||||
eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0)
|
eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0)
|
||||||
@ -19738,7 +19747,7 @@ snapshots:
|
|||||||
enhanced-resolve: 5.15.0
|
enhanced-resolve: 5.15.0
|
||||||
eslint: 8.57.0
|
eslint: 8.57.0
|
||||||
eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
||||||
eslint-plugin-import: 2.29.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
eslint-plugin-import: 2.29.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
|
||||||
fast-glob: 3.3.1
|
fast-glob: 3.3.1
|
||||||
get-tsconfig: 4.7.2
|
get-tsconfig: 4.7.2
|
||||||
is-core-module: 2.13.1
|
is-core-module: 2.13.1
|
||||||
@ -19777,7 +19786,7 @@ snapshots:
|
|||||||
eslint: 8.57.0
|
eslint: 8.57.0
|
||||||
ignore: 5.3.1
|
ignore: 5.3.1
|
||||||
|
|
||||||
eslint-plugin-import@2.29.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
|
eslint-plugin-import@2.29.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
array-includes: 3.1.7
|
array-includes: 3.1.7
|
||||||
array.prototype.findlastindex: 1.2.3
|
array.prototype.findlastindex: 1.2.3
|
||||||
@ -24083,6 +24092,11 @@ snapshots:
|
|||||||
minimist: 1.2.8
|
minimist: 1.2.8
|
||||||
strip-json-comments: 2.0.1
|
strip-json-comments: 2.0.1
|
||||||
|
|
||||||
|
react-colorful@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
|
||||||
react-dev-utils@12.0.1(eslint@8.57.0)(typescript@5.3.3)(webpack@5.95.0):
|
react-dev-utils@12.0.1(eslint@8.57.0)(typescript@5.3.3)(webpack@5.95.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.22.13
|
'@babel/code-frame': 7.22.13
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user