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() {
|
||||
const session = await getServerAuthSession();
|
||||
if (session) {
|
||||
redirect("/dashboard/settings");
|
||||
redirect("/dashboard/today");
|
||||
} else {
|
||||
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,
|
||||
{
|
||||
name: "Tags",
|
||||
name: "Labels",
|
||||
icon: <Tag size={18} />,
|
||||
path: "/dashboard/tags",
|
||||
},
|
||||
{
|
||||
name: "Archive",
|
||||
icon: <Archive size={18} />,
|
||||
path: "/dashboard/archive",
|
||||
path: "/dashboard/labels",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -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",
|
||||
"@emoji-mart/data": "^1.1.2",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@lifetracker/db": "workspace:^",
|
||||
"@lifetracker/shared": "workspace:^0.1.0",
|
||||
"@lifetracker/shared-react": "workspace:^0.1.0",
|
||||
"@lifetracker/trpc": "workspace:^",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
@ -58,6 +58,7 @@
|
||||
"next-themes": "^0.3.0",
|
||||
"prettier": "^3.2.5",
|
||||
"react": "^18.2.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-draggable": "^4.4.6",
|
||||
"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,
|
||||
"tag": "0004_sad_sally_floyd",
|
||||
"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(),
|
||||
});
|
||||
|
||||
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", {
|
||||
key: text("key").notNull().primaryKey(),
|
||||
value: text("value").notNull(),
|
||||
@ -118,3 +138,17 @@ export const apiKeyRelations = relations(apiKeys, ({ one }) => ({
|
||||
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 { apiKeysAppRouter } from "./apiKeys";
|
||||
import { adminAppRouter } from "./admin";
|
||||
import { labelsAppRouter } from "./labels";
|
||||
|
||||
export const appRouter = router({
|
||||
users: usersAppRouter,
|
||||
apiKeys: apiKeysAppRouter,
|
||||
admin: adminAppRouter,
|
||||
labels: labelsAppRouter,
|
||||
});
|
||||
// export type definition of API
|
||||
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:
|
||||
specifier: ^18.2.0
|
||||
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:
|
||||
specifier: ^18.2.0
|
||||
version: 18.3.1(react@18.3.1)
|
||||
@ -9707,6 +9710,12 @@ packages:
|
||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==}
|
||||
engines: {node: '>=14'}
|
||||
@ -19684,7 +19693,7 @@ snapshots:
|
||||
eslint: 8.57.0
|
||||
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-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-react: 7.33.2(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
|
||||
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
|
||||
get-tsconfig: 4.7.2
|
||||
is-core-module: 2.13.1
|
||||
@ -19777,7 +19786,7 @@ snapshots:
|
||||
eslint: 8.57.0
|
||||
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:
|
||||
array-includes: 3.1.7
|
||||
array.prototype.findlastindex: 1.2.3
|
||||
@ -24083,6 +24092,11 @@ snapshots:
|
||||
minimist: 1.2.8
|
||||
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):
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.22.13
|
||||
|
||||
Loading…
Reference in New Issue
Block a user