From 4af1f356879953365fde7cd04625bdb6a3b1cad9 Mon Sep 17 00:00:00 2001 From: Ryan Pandya Date: Tue, 26 Nov 2024 08:50:12 -0800 Subject: [PATCH] Basic Day functionality works in CLI and Web (getOrCreate, update Mood, Comment) --- apps/cli/package.json | 6 +- apps/cli/src/commands/days.ts | 61 ++ apps/cli/src/index.ts | 2 + apps/web/app/dashboard/bookmarks/page.tsx | 10 - .../app/dashboard/day/[dateQuery]/page.tsx | 32 + .../web/app/dashboard/{today => day}/page.tsx | 2 +- apps/web/app/page.tsx | 2 +- .../web/components/dashboard/EditableText.tsx | 14 +- .../components/dashboard/UploadDropzone.tsx | 146 ---- .../bookmarks/UpdatableBookmarksGrid.tsx | 52 -- .../web/components/dashboard/days/DayView.tsx | 45 + .../dashboard/days/EditableDayComment.tsx | 56 ++ apps/web/package.json | 1 + packages/db/drizzle.ts | 4 +- ...d_cuckoos.sql => 0000_powerful_zodiak.sql} | 20 +- .../db/migrations/meta/0000_snapshot.json | 122 ++- packages/db/migrations/meta/_journal.json | 4 +- packages/db/schema.ts | 68 +- packages/shared-react/hooks/days.ts | 43 + packages/shared-react/package.json | 1 + packages/shared/colors.ts | 0 packages/shared/types/days.ts | 9 + packages/trpc/auth.ts | 13 - packages/trpc/package.json | 2 + packages/trpc/routers/_app.ts | 2 + packages/trpc/routers/days.ts | 105 +++ pnpm-lock.yaml | 28 + scripts/apiKey.json | 10 + scripts/category.json | 792 ++++++++++++++++++ scripts/color.json | 90 ++ scripts/user.json | 11 + 31 files changed, 1495 insertions(+), 258 deletions(-) create mode 100644 apps/cli/src/commands/days.ts delete mode 100644 apps/web/app/dashboard/bookmarks/page.tsx create mode 100644 apps/web/app/dashboard/day/[dateQuery]/page.tsx rename apps/web/app/dashboard/{today => day}/page.tsx (72%) delete mode 100644 apps/web/components/dashboard/UploadDropzone.tsx delete mode 100644 apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx create mode 100644 apps/web/components/dashboard/days/DayView.tsx create mode 100644 apps/web/components/dashboard/days/EditableDayComment.tsx rename packages/db/migrations/{0000_awesome_stepford_cuckoos.sql => 0000_powerful_zodiak.sql} (82%) create mode 100644 packages/shared-react/hooks/days.ts delete mode 100644 packages/shared/colors.ts create mode 100644 packages/shared/types/days.ts create mode 100644 packages/trpc/routers/days.ts create mode 100644 scripts/apiKey.json create mode 100644 scripts/category.json create mode 100644 scripts/color.json create mode 100644 scripts/user.json diff --git a/apps/cli/package.json b/apps/cli/package.json index c155b81..865325e 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -16,11 +16,13 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@date-fns/tz": "^1.2.0", "@lifetracker/db": "workspace:*", "@lifetracker/trpc": "workspace:^", + "date-fns": "^4.1.0", + "dotenv": "^16.4.1", "table": "^6.8.2", - "vite-tsconfig-paths": "^5.1.0", - "dotenv": "^16.4.1" + "vite-tsconfig-paths": "^5.1.0" }, "devDependencies": { "@commander-js/extra-typings": "^12.1.0", diff --git a/apps/cli/src/commands/days.ts b/apps/cli/src/commands/days.ts new file mode 100644 index 0000000..32c9797 --- /dev/null +++ b/apps/cli/src/commands/days.ts @@ -0,0 +1,61 @@ +import { getGlobalOptions } from "@/lib/globals"; +import { + printError, + printErrorMessageWithReason, + printObject, + printSuccess, +} from "@/lib/output"; +import { getAPIClient } from "@/lib/trpc"; +import { Command } from "@commander-js/extra-typings"; +import { getBorderCharacters, table } from "table"; +import { format } from "date-fns"; +import { TZDate } from "@date-fns/tz"; + +function moodToStars(mood: number) { + // const full_stars = Math.floor(mood / 2); + // const half_star = mood % 2; + // return "★".repeat(full_stars) + (half_star ? "⯨" : ""); + return "★".repeat(mood) + "☆".repeat(10 - mood); +} + +export const daysCmd = new Command() + .name("day") + .description("Get data for a specific day") + .argument('', 'A date in ISO-8601 format, or "yesterday", "today", "tomorrow", etc.') + .option('-c, --comment ', "edit this day's comment") + .option('-m, --mood ', "edit this day's mood") + .action(async (dateQuery: string, flags?) => { + const api = getAPIClient(); + + try { + if (flags?.comment || flags?.mood) { + const updateProps = { dateQuery: dateQuery, ...flags }; + await api.days.update.mutate(updateProps); + } + + const day = (await api.days.get.query({ dateQuery: dateQuery })); + + if (getGlobalOptions().json) { + printObject(day); + } else { + + const moodStr = moodToStars(day.mood); + const dateStr = format(`${day.date}T00:00:00`, "EEEE, MMMM do"); + const data: string[][] = [[dateStr, moodStr], [day.comment ?? "No comment", '',]]; + + console.log(table(data, { + // border: getBorderCharacters("ramac"), + // singleLine: true, + spanningCells: [{ col: 0, row: 1, colSpan: 2 }], + drawVerticalLine: (lineIndex, columnCount) => { + return lineIndex === 0 || lineIndex === columnCount || (lineIndex === 0 && columnCount === 2); + }, + drawHorizontalLine: (lineIndex, rowCount) => { + return (lineIndex < 2 || lineIndex === rowCount); + }, + })); + } + } catch (error) { + printErrorMessageWithReason("Failed to get day", error as object); + } + }); diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 62a840a..a0b17fb 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -4,6 +4,7 @@ import { Command, Option } from "@commander-js/extra-typings"; import { whoamiCmd } from "@/commands/whoami"; import { colorsCmd } from "@/commands/colors"; import { categoriesCmd } from "@/commands/categories"; +import { daysCmd } from "@/commands/days"; import { config } from "dotenv"; @@ -36,6 +37,7 @@ const program = new Command() ); program.addCommand(whoamiCmd); +program.addCommand(daysCmd); program.addCommand(colorsCmd); program.addCommand(categoriesCmd); diff --git a/apps/web/app/dashboard/bookmarks/page.tsx b/apps/web/app/dashboard/bookmarks/page.tsx deleted file mode 100644 index a7b22fe..0000000 --- a/apps/web/app/dashboard/bookmarks/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; -import Bookmarks from "@/components/dashboard/bookmarks/Bookmarks"; - -export default async function BookmarksPage() { - return ( -
- -
- ); -} diff --git a/apps/web/app/dashboard/day/[dateQuery]/page.tsx b/apps/web/app/dashboard/day/[dateQuery]/page.tsx new file mode 100644 index 0000000..ac653af --- /dev/null +++ b/apps/web/app/dashboard/day/[dateQuery]/page.tsx @@ -0,0 +1,32 @@ +import { notFound } from "next/navigation"; +import { api } from "@/server/api/client"; +import { TRPCError } from "@trpc/server"; +import DayView from "@/components/dashboard/days/DayView"; +import LoadingSpinner from "@/components/ui/spinner"; + +export default async function DayPage({ + params, +}: { + params: { dateQuery: string }; +}) { + let day; + try { + day = await api.days.get({ dateQuery: params.dateQuery }); + } catch (e) { + if (e instanceof TRPCError) { + if (e.code == "NOT_FOUND") { + notFound(); + } + } + throw e; + } + + return ( + params.dateQuery === undefined ? + + : + + ); +} diff --git a/apps/web/app/dashboard/today/page.tsx b/apps/web/app/dashboard/day/page.tsx similarity index 72% rename from apps/web/app/dashboard/today/page.tsx rename to apps/web/app/dashboard/day/page.tsx index b85dc83..75c8736 100644 --- a/apps/web/app/dashboard/today/page.tsx +++ b/apps/web/app/dashboard/day/page.tsx @@ -1,6 +1,6 @@ import React from "react"; -export default async function TodayPage() { +export default async function MainDayPage() { return (
Hello from a logged in page! diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 0ce5bdf..7002ea7 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -4,7 +4,7 @@ import { getServerAuthSession } from "@/server/auth"; export default async function Home() { const session = await getServerAuthSession(); if (session) { - redirect("/dashboard/today"); + redirect("/dashboard/day/today"); } else { redirect("/signin"); } diff --git a/apps/web/components/dashboard/EditableText.tsx b/apps/web/components/dashboard/EditableText.tsx index 55ce10c..94b1f26 100644 --- a/apps/web/components/dashboard/EditableText.tsx +++ b/apps/web/components/dashboard/EditableText.tsx @@ -98,16 +98,14 @@ function ViewMode({ return (
- - {originalText ? ( -

{originalText}

- ) : ( -

Untitled

- )} -
+ {originalText ? ( +

{originalText}

+ ) : ( +

Untitled

+ )} { - if (resp.alreadyExists) { - toast({ - description: , - variant: "default", - }); - } else { - toast({ description: "Bookmark uploaded" }); - } - }, - onError: () => { - toast({ description: "Something went wrong", variant: "destructive" }); - }, - }); - - const { mutateAsync: runUploadAsset } = useUpload({ - onSuccess: async (resp) => { - const assetType = - resp.contentType === "application/pdf" ? "pdf" : "image"; - await createBookmark({ ...resp, type: BookmarkTypes.ASSET, assetType }); - }, - onError: (err, req) => { - toast({ - description: `${req.name}: ${err.error}`, - variant: "destructive", - }); - }, - }); - - return useCallback( - (file: File) => { - return runUploadAsset(file); - }, - [runUploadAsset], - ); -} - -function useUploadAssets({ - onFileUpload, - onFileError, - onAllUploaded, -}: { - onFileUpload: () => void; - onFileError: (name: string, e: Error) => void; - onAllUploaded: () => void; -}) { - const runUpload = useUploadAsset(); - - return async (files: File[]) => { - if (files.length == 0) { - return; - } - for (const file of files) { - try { - await runUpload(file); - onFileUpload(); - } catch (e) { - if (e instanceof TRPCClientError || e instanceof Error) { - onFileError(file.name, e); - } - } - } - onAllUploaded(); - }; -} - -export default function UploadDropzone({ - children, -}: { - children: React.ReactNode; -}) { - const [numUploading, setNumUploading] = useState(0); - const [numUploaded, setNumUploaded] = useState(0); - const uploadAssets = useUploadAssets({ - onFileUpload: () => { - setNumUploaded((c) => c + 1); - }, - onFileError: () => { - setNumUploaded((c) => c + 1); - }, - onAllUploaded: () => { - setNumUploading(0); - setNumUploaded(0); - return; - }, - }); - - const [isDragging, setDragging] = useState(false); - const onDrop = (acceptedFiles: File[]) => { - uploadAssets(acceptedFiles); - setNumUploading(acceptedFiles.length); - setDragging(false); - }; - - return ( - setDragging(true)} - onDragLeave={() => setDragging(false)} - > - {({ getRootProps, getInputProps }) => ( -
- -
0 ? undefined : "hidden", - )} - > - {numUploading > 0 ? ( -
-

- Uploading {numUploaded} / {numUploading} -

- -
- ) : ( -

- Drop Your Image / Bookmark file -

- )} -
- {children} -
- )} -
- ); -} diff --git a/apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx b/apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx deleted file mode 100644 index bc6cd6d..0000000 --- a/apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx +++ /dev/null @@ -1,52 +0,0 @@ -"use client"; - -import UploadDropzone from "@/components/dashboard/UploadDropzone"; -import { api } from "@/lib/trpc"; - -import type { - ZGetBookmarksRequest, - ZGetBookmarksResponse, -} from "@hoarder/shared/types/bookmarks"; -import { BookmarkGridContextProvider } from "@hoarder/shared-react/hooks/bookmark-grid-context"; - -import BookmarksGrid from "./BookmarksGrid"; - -export default function UpdatableBookmarksGrid({ - query, - bookmarks: initialBookmarks, - showEditorCard = false, -}: { - query: ZGetBookmarksRequest; - bookmarks: ZGetBookmarksResponse; - showEditorCard?: boolean; - itemsPerPage?: number; -}) { - const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = - api.bookmarks.getBookmarks.useInfiniteQuery( - { ...query, useCursorV2: true }, - { - initialData: () => ({ - pages: [initialBookmarks], - pageParams: [query.cursor], - }), - initialCursor: null, - getNextPageParam: (lastPage) => lastPage.nextCursor, - }, - ); - - const grid = ( - b.bookmarks)} - hasNextPage={hasNextPage} - fetchNextPage={() => fetchNextPage()} - isFetchingNextPage={isFetchingNextPage} - showEditorCard={showEditorCard} - /> - ); - - return ( - - {showEditorCard ? {grid} : grid} - - ); -} diff --git a/apps/web/components/dashboard/days/DayView.tsx b/apps/web/components/dashboard/days/DayView.tsx new file mode 100644 index 0000000..1ac0865 --- /dev/null +++ b/apps/web/components/dashboard/days/DayView.tsx @@ -0,0 +1,45 @@ +import { redirect } from "next/navigation"; +import { Separator } from "@/components/ui/separator"; +import { api } from "@/server/api/client"; +import { getServerAuthSession } from "@/server/auth"; +import { ZDay } from "@lifetracker/shared/types/days"; +import EditableDayComment from "./EditableDayComment"; +import { format } from "date-fns"; + +export default async function DayView({ + day, + header, + showDivider, + showEditorCard = false, +}: { + day: ZDay; + header?: React.ReactNode; + showDivider?: boolean; + showEditorCard?: boolean; +}) { + const session = await getServerAuthSession(); + if (!session) { + redirect("/"); + } + + // const entries = await api.entries.get({ day: day }); + + return ( +
+ +
+ + {format(`${day.date}T00:00:00`, "EEEE, MMMM do")} + + +
+ + +
Rating: {day.mood?.toString() ?? "-"}
+ + +
+ ); +} diff --git a/apps/web/components/dashboard/days/EditableDayComment.tsx b/apps/web/components/dashboard/days/EditableDayComment.tsx new file mode 100644 index 0000000..594a089 --- /dev/null +++ b/apps/web/components/dashboard/days/EditableDayComment.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { usePathname, useRouter } from "next/navigation"; +import { toast } from "@/components/ui/use-toast"; +import { cn } from "@/lib/utils"; + +import { useUpdateDay } from "@lifetracker/shared-react/hooks/days"; +import { EditableText } from "../EditableText"; +import { format } from "date-fns"; + +export default function EditableDayComment({ + day, + className, +}: { + day: { id: string; date: string, comment: string }; + className?: string; +}) { + const router = useRouter(); + const currentPath = usePathname(); + const { mutate: updateDay, isPending } = useUpdateDay({ + onSuccess: () => { + toast({ + description: "Day updated!", + }); + if (currentPath.includes("dashboard")) { + router.refresh(); + } + }, + }); + return ( + { + if (!newComment) { return } + updateDay( + { + dateQuery: format(`${day.date}T00:00:00`, "yyyy-MM-dd"), + comment: newComment, + }, + { + onError: (e) => { + console.log(e); + toast({ + description: e.message, + variant: "destructive", + }); + }, + }, + ); + }} + isSaving={isPending} + /> + ); +} \ No newline at end of file diff --git a/apps/web/package.json b/apps/web/package.json index 71b2405..8aa5280 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -49,6 +49,7 @@ "clsx": "^2.1.0", "color-2-name": "^1.4.4", "csv-parse": "^5.5.6", + "date-fns": "^4.1.0", "dayjs": "^1.11.10", "drizzle-orm": "^0.33.0", "fastest-levenshtein": "^1.0.16", diff --git a/packages/db/drizzle.ts b/packages/db/drizzle.ts index 01d3f08..26bcfc0 100644 --- a/packages/db/drizzle.ts +++ b/packages/db/drizzle.ts @@ -7,12 +7,10 @@ import path from "path"; import dbConfig from "./drizzle.config"; -console.log("dbURL: ", dbConfig.dbCredentials.url); - const sqlite = new Database(dbConfig.dbCredentials.url); export const db = drizzle(sqlite, { schema, - logger: true + logger: false }); export function getInMemoryDB(runMigrations: boolean) { diff --git a/packages/db/migrations/0000_awesome_stepford_cuckoos.sql b/packages/db/migrations/0000_powerful_zodiak.sql similarity index 82% rename from packages/db/migrations/0000_awesome_stepford_cuckoos.sql rename to packages/db/migrations/0000_powerful_zodiak.sql index b2b7499..2ae88a9 100644 --- a/packages/db/migrations/0000_awesome_stepford_cuckoos.sql +++ b/packages/db/migrations/0000_powerful_zodiak.sql @@ -54,10 +54,23 @@ CREATE TABLE `config` ( ); --> statement-breakpoint CREATE TABLE `day` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `mood` integer, + `id` text PRIMARY KEY NOT NULL, `date` text NOT NULL, - `comment` text NOT NULL + `mood` integer, + `comment` text +); +--> statement-breakpoint +CREATE TABLE `entry` ( + `id` text PRIMARY KEY NOT NULL, + `createdAt` integer NOT NULL, + `userId` text NOT NULL, + `comment` text, + `dayId` integer, + `startedAt` integer NOT NULL, + `endedAt` integer NOT NULL, + `categoryId` text NOT NULL, + FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`categoryId`) REFERENCES `category`(`id`) ON UPDATE no action ON DELETE no action ); --> statement-breakpoint CREATE TABLE `session` ( @@ -89,4 +102,5 @@ CREATE UNIQUE INDEX `apiKey_name_userId_unique` ON `apiKey` (`name`,`userId`);-- CREATE UNIQUE INDEX `category_userId_code_unique` ON `category` (`userId`,`code`);--> statement-breakpoint CREATE UNIQUE INDEX `color_userId_name_unique` ON `color` (`userId`,`name`);--> statement-breakpoint CREATE UNIQUE INDEX `day_date_unique` ON `day` (`date`);--> statement-breakpoint +CREATE UNIQUE INDEX `entry_userId_startedAt_endedAt_unique` ON `entry` (`userId`,`startedAt`,`endedAt`);--> statement-breakpoint CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`); \ No newline at end of file diff --git a/packages/db/migrations/meta/0000_snapshot.json b/packages/db/migrations/meta/0000_snapshot.json index 4ea3071..c61c882 100644 --- a/packages/db/migrations/meta/0000_snapshot.json +++ b/packages/db/migrations/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "7f1d6157-2405-4b96-be6a-a4bfb35f9aaf", + "id": "ceed45ad-37d0-4e59-92bd-3be02d271535", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "account": { @@ -409,16 +409,9 @@ "columns": { "id": { "name": "id", - "type": "integer", + "type": "text", "primaryKey": true, "notNull": true, - "autoincrement": true - }, - "mood": { - "name": "mood", - "type": "integer", - "primaryKey": false, - "notNull": false, "autoincrement": false }, "date": { @@ -428,11 +421,18 @@ "notNull": true, "autoincrement": false }, + "mood": { + "name": "mood", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, "comment": { "name": "comment", "type": "text", "primaryKey": false, - "notNull": true, + "notNull": false, "autoincrement": false } }, @@ -449,6 +449,108 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, + "entry": { + "name": "entry", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "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 + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dayId": { + "name": "dayId", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "startedAt": { + "name": "startedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "endedAt": { + "name": "endedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "categoryId": { + "name": "categoryId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "entry_userId_startedAt_endedAt_unique": { + "name": "entry_userId_startedAt_endedAt_unique", + "columns": [ + "userId", + "startedAt", + "endedAt" + ], + "isUnique": true + } + }, + "foreignKeys": { + "entry_userId_user_id_fk": { + "name": "entry_userId_user_id_fk", + "tableFrom": "entry", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entry_categoryId_category_id_fk": { + "name": "entry_categoryId_category_id_fk", + "tableFrom": "entry", + "tableTo": "category", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, "session": { "name": "session", "columns": { diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index ce44a57..6e887c6 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "6", - "when": 1732441653703, - "tag": "0000_awesome_stepford_cuckoos", + "when": 1732544381615, + "tag": "0000_powerful_zodiak", "breakpoints": true } ] diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 14130a6..31907b5 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -1,6 +1,7 @@ import type { AdapterAccount } from "@auth/core/adapters"; import { createId } from "@paralleldrive/cuid2"; import { relations, SQL, sql } from "drizzle-orm"; +import { date } from "drizzle-orm/mysql-core"; import { AnySQLiteColumn, index, @@ -122,10 +123,13 @@ export const verificationTokens = sqliteTable( ); export const days = sqliteTable("day", { - id: integer("id").primaryKey({ autoIncrement: true }), - mood: integer("mood"), + id: text("id") + .notNull() + .primaryKey() + .$defaultFn(() => createId()), date: text("date").notNull().unique(), - comment: text("comment").notNull(), + mood: integer("mood"), + comment: text("comment"), }); export const colors = sqliteTable( @@ -175,6 +179,28 @@ export const categories = sqliteTable( }), ); +export const entries = sqliteTable( + "entry", + { + id: text("id") + .notNull() + .primaryKey() + .$defaultFn(() => createId()), + createdAt: createdAtField(), + userId: text("userId") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + comment: text("comment"), + dayId: integer("dayId"), + startedAt: integer("startedAt", { mode: "timestamp" }).notNull(), + endedAt: integer("endedAt", { mode: "timestamp" }).notNull(), + categoryId: text("categoryId").notNull().references(() => categories.id), + }, + (e) => ({ + uniq: unique().on(e.userId, e.startedAt, e.endedAt) + }), +) + // Relations export const apiKeyRelations = relations(apiKeys, ({ one }) => ({ @@ -186,9 +212,21 @@ export const apiKeyRelations = relations(apiKeys, ({ one }) => ({ export const userRelations = relations(users, ({ many }) => ({ categories: many(categories), colors: many(colors), + entries: many(entries), })); +export const colorsRelations = relations( + colors, + ({ many, one }) => ({ + user: one(users, { + fields: [colors.userId], + references: [users.id], + }), + categories: many(categories), + }), +); + export const categoriesRelations = relations( categories, ({ many, one }) => ({ @@ -204,16 +242,32 @@ export const categoriesRelations = relations( fields: [categories.parentId], references: [categories.id], }), + entries: many(entries), }), ); -export const colorsRelations = relations( - colors, +export const daysRelations = relations( + days, + ({ many }) => ({ + entries: many(entries), + }), +); + +export const entriesRelations = relations( + entries, ({ many, one }) => ({ user: one(users, { - fields: [colors.userId], + fields: [entries.userId], references: [users.id], }), - categories: many(categories), + categories: one(categories, { + fields: [entries.categoryId], + references: [categories.id], + } + ), + day: one(days, { + fields: [entries.dayId], + references: [days.id], + }), }), ); \ No newline at end of file diff --git a/packages/shared-react/hooks/days.ts b/packages/shared-react/hooks/days.ts new file mode 100644 index 0000000..0a10eef --- /dev/null +++ b/packages/shared-react/hooks/days.ts @@ -0,0 +1,43 @@ +import { api } from "../trpc"; + +export function useUpdateDay( + ...opts: Parameters +) { + const apiUtils = api.useUtils(); + return api.days.update.useMutation({ + ...opts[0], + onSuccess: (res, req, meta) => { + apiUtils.days.get.invalidate({ dateQuery: req.dateQuery }); + return opts[0]?.onSuccess?.(res, req, meta); + }, + }); +} + +export function useDeleteLabel( + ...opts: Parameters +) { + const apiUtils = api.useUtils(); + + return api.labels.delete.useMutation({ + ...opts[0], + onSuccess: (res, req, meta) => { + apiUtils.labels.list.invalidate(); + // apiUtils.bookmarks.getBookmark.invalidate(); + return opts[0]?.onSuccess?.(res, req, meta); + }, + }); +} + +export function useDeleteUnusedTags( + ...opts: Parameters +) { + const apiUtils = api.useUtils(); + + return api.tags.deleteUnused.useMutation({ + ...opts[0], + onSuccess: (res, req, meta) => { + apiUtils.tags.list.invalidate(); + return opts[0]?.onSuccess?.(res, req, meta); + }, + }); +} diff --git a/packages/shared-react/package.json b/packages/shared-react/package.json index 15a492e..e20f3c4 100644 --- a/packages/shared-react/package.json +++ b/packages/shared-react/package.json @@ -8,6 +8,7 @@ "@lifetracker/trpc": "workspace:^", "@tanstack/react-query": "^5.24.8", "@trpc/client": "11.0.0-next-beta.308", + "@trpc/react-query": "11.0.0-next-beta.308", "superjson": "^2.2.1" }, "devDependencies": { diff --git a/packages/shared/colors.ts b/packages/shared/colors.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/shared/types/days.ts b/packages/shared/types/days.ts new file mode 100644 index 0000000..4dc0a4a --- /dev/null +++ b/packages/shared/types/days.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; + +export const zDaySchema = z.object({ + id: z.string().optional(), + date: z.string(), + mood: z.number().nullable(), + comment: z.string().nullable(), +}); +export type ZDay = z.infer; diff --git a/packages/trpc/auth.ts b/packages/trpc/auth.ts index 97b637b..628dee0 100644 --- a/packages/trpc/auth.ts +++ b/packages/trpc/auth.ts @@ -56,19 +56,6 @@ function parseApiKey(plain: string) { export async function authenticateApiKey(key: string) { const { keyId, keySecret } = parseApiKey(key); - - console.log("\n\nWELCOME TO HELL\n\n"); - console.log(parseApiKey(key)); - // Console.log all rows in the apiKeys table - console.log(await db.query.apiKeys.findMany()); - console.log(await db.query.apiKeys.findFirst({ - where: (k, { eq }) => eq(k.keyId, keyId), - with: { - user: true, - }, - })); - console.log("\n\n\n\n"); - const apiKey = await db.query.apiKeys.findFirst({ where: (k, { eq }) => eq(k.keyId, keyId), with: { diff --git a/packages/trpc/package.json b/packages/trpc/package.json index 2a3779e..deb3062 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -10,11 +10,13 @@ "test": "vitest" }, "dependencies": { + "@date-fns/tz": "^1.2.0", "@lifetracker/db": "workspace:*", "@lifetracker/shared": "workspace:^", "@lifetracker/ui": "workspace:*", "@trpc/server": "^11.0.0-next-beta.308", "bcryptjs": "^2.4.3", + "date-fns": "^4.1.0", "drizzle-orm": "^0.33.0", "superjson": "^2.2.1", "tiny-invariant": "^1.3.3", diff --git a/packages/trpc/routers/_app.ts b/packages/trpc/routers/_app.ts index 9f6ff43..f426b43 100644 --- a/packages/trpc/routers/_app.ts +++ b/packages/trpc/routers/_app.ts @@ -5,11 +5,13 @@ import { apiKeysAppRouter } from "./apiKeys"; import { adminAppRouter } from "./admin"; import { categoriesAppRouter } from "./categories"; import { colorsAppRouter } from "./colors"; +import { daysAppRouter } from "./days"; export const appRouter = router({ users: usersAppRouter, apiKeys: apiKeysAppRouter, admin: adminAppRouter, + days: daysAppRouter, colors: colorsAppRouter, categories: categoriesAppRouter, }); diff --git a/packages/trpc/routers/days.ts b/packages/trpc/routers/days.ts new file mode 100644 index 0000000..65ce969 --- /dev/null +++ b/packages/trpc/routers/days.ts @@ -0,0 +1,105 @@ +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 { days, } from "@lifetracker/db/schema"; +import { + zDaySchema, ZDay +} from "@lifetracker/shared/types/days"; +import type { Context } from "../index"; +import { authedProcedure, router } from "../index"; +import { format } from "date-fns"; +import { TZDate } from "@date-fns/tz"; + +function dateFromInput(input: { dateQuery: string }) { + let t: string; + console.log(input.dateQuery); + if (input.dateQuery == "today") { + t = TZDate.tz("America/Los_Angeles"); + } + else { + t = new TZDate(input.dateQuery, "Etc/UTC"); + } + + console.log(format(t, "yyyy-MM-dd")); + return format(t, "yyyy-MM-dd"); +} + +async function createDay(date: string, ctx: Context) { + return await ctx.db.transaction(async (trx) => { + try { + const result = await trx + .insert(days) + .values({ + date: date, + }) + .returning({ + id: days.id, + date: days.date, + mood: days.mood, + comment: days.comment, + }); + return result[0]; + } catch (e) { + console.log(e); + if (e instanceof SqliteError) { + if (e.code == "SQLITE_CONSTRAINT_UNIQUE") { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "This day already exists", + }); + } + } + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Something went wrong", + }); + } + }); +} + +export const daysAppRouter = router({ + get: authedProcedure + .input(z.object({ + dateQuery: z.string(), + })) + .output(zDaySchema) + .query(async ({ input, ctx }) => { + const date = dateFromInput(input); + + const res = await ctx.db + .select({ + id: days.id, + date: days.date, + mood: days.mood, + comment: days.comment, + }) + .from(days) + .where(eq(days.date, date)); + + const day = res.length == 0 ? createDay(date, ctx) : res[0]; + return day; + }), + update: authedProcedure + .input( + z.object({ + mood: z.string().optional().or(z.number()), + comment: z.string().optional(), + dateQuery: z.string(), + }), + ) + .mutation(async ({ input, ctx }) => { + const { dateQuery, ...updatedProps } = input; + + // Convert mood to number, if it exists + if (updatedProps.mood) { + updatedProps.mood = parseInt(updatedProps.mood); + } + console.log(dateQuery, "::", dateFromInput({ dateQuery: dateQuery })); + await ctx.db + .update(days) + .set(updatedProps) + .where(eq(days.date, dateFromInput({ dateQuery: dateQuery }))); + }), +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2cd92c8..56fb564 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,12 +20,18 @@ importers: apps/cli: dependencies: + '@date-fns/tz': + specifier: ^1.2.0 + version: 1.2.0 '@lifetracker/db': specifier: workspace:* version: link:../../packages/db '@lifetracker/trpc': specifier: workspace:^ version: link:../../packages/trpc + date-fns: + specifier: ^4.1.0 + version: 4.1.0 dotenv: specifier: ^16.4.1 version: 16.4.5 @@ -235,6 +241,9 @@ importers: csv-parse: specifier: ^5.5.6 version: 5.5.6 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 dayjs: specifier: ^1.11.10 version: 1.11.13 @@ -482,6 +491,9 @@ importers: '@trpc/client': specifier: 11.0.0-next-beta.308 version: 11.0.0-next-beta.308(@trpc/server@11.0.0-next-beta.308) + '@trpc/react-query': + specifier: 11.0.0-next-beta.308 + version: 11.0.0-next-beta.308(@tanstack/react-query@5.60.2(react@18.3.1))(@trpc/client@11.0.0-next-beta.308(@trpc/server@11.0.0-next-beta.308))(@trpc/server@11.0.0-next-beta.308)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: '*' version: 18.3.1 @@ -544,6 +556,9 @@ importers: packages/trpc: dependencies: + '@date-fns/tz': + specifier: ^1.2.0 + version: 1.2.0 '@lifetracker/db': specifier: workspace:* version: link:../db @@ -559,6 +574,9 @@ importers: bcryptjs: specifier: ^2.4.3 version: 2.4.3 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 drizzle-orm: specifier: ^0.33.0 version: 0.33.0(@types/better-sqlite3@7.6.11)(@types/react@18.2.61)(better-sqlite3@11.5.0)(expo-sqlite@14.0.6(expo@51.0.36(@babel/core@7.26.0)(@babel/preset-env@7.25.7(@babel/core@7.26.0))))(react@18.3.1) @@ -1896,6 +1914,9 @@ packages: '@dabh/diagnostics@2.0.3': resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + '@date-fns/tz@1.2.0': + resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==} + '@discoveryjs/json-ext@0.5.7': resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} @@ -5683,6 +5704,9 @@ packages: resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} engines: {node: '>= 0.4'} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} @@ -14179,6 +14203,8 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 + '@date-fns/tz@1.2.0': {} + '@discoveryjs/json-ext@0.5.7': {} '@docsearch/css@3.6.2': {} @@ -19106,6 +19132,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.1 + date-fns@4.1.0: {} + dayjs@1.11.13: {} debounce@1.2.1: {} diff --git a/scripts/apiKey.json b/scripts/apiKey.json new file mode 100644 index 0000000..ce36ec0 --- /dev/null +++ b/scripts/apiKey.json @@ -0,0 +1,10 @@ +[ + { + "createdAt": 1731634820, + "id": "wxl62dg2n721tgzlz3spnjbu", + "keyHash": "$2a$10$NhOG42FjMbDycWHcJI4LH.Jp.aCV.op7llIP0zw/CBUmB3lO0HHQu", + "keyId": "b9b17eb909ce0ecdbf33", + "name": "CLI App", + "userId": "n7yns1s5b122y5e8v0p6uyt8" + } +] \ No newline at end of file diff --git a/scripts/category.json b/scripts/category.json new file mode 100644 index 0000000..bd7aff0 --- /dev/null +++ b/scripts/category.json @@ -0,0 +1,792 @@ +[ + { + "code": 0.0, + "colorId": "ej29qv1e7l1dnfxajl0561dt", + "createdAt": 1732442037, + "description": "Time spent sleeping.", + "id": "pjb9vypaflgdcl3w9cn59bfy", + "name": "Sleep", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 0.4, + "colorId": "ej29qv1e7l1dnfxajl0561dt", + "createdAt": 1732442768, + "description": "Typically shitty half-sleep on a plane or in a car.", + "id": "iazlvs3xxaml3viytgcisvbl", + "name": "Travel sleep", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 0.5, + "colorId": "ej29qv1e7l1dnfxajl0561dt", + "createdAt": 1732443778, + "description": "Sleep other than at night.", + "id": "orqt6lk4jyy0578e9vt3jn37", + "name": "Nap", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 0.9, + "colorId": "ej29qv1e7l1dnfxajl0561dt", + "createdAt": 1732443779, + "description": "Resting while sick or injured.", + "id": "qtfjtrpbbz7adlyd2ynn4tq6", + "name": "Sick", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 1.0, + "colorId": "ahapbp9ujym8lgf4ny02i26h", + "createdAt": 1732444271, + "description": "Time spent with family.", + "id": "iwja5v39r48weawdp5rmomb1", + "name": "Family", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 13.0, + "colorId": "ahapbp9ujym8lgf4ny02i26h", + "createdAt": 1732444273, + "description": "Wedding and related stuff.", + "id": "vwihwohm09c1zsoh6rnddw0m", + "name": "Wedding, etc.", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 15.0, + "colorId": "ahapbp9ujym8lgf4ny02i26h", + "createdAt": 1732444274, + "description": "Local family, if you will.", + "id": "ontipjpabhqx5wlocjab0xpd", + "name": "Neighborhood / Community", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 16.0, + "colorId": "ahapbp9ujym8lgf4ny02i26h", + "createdAt": 1732444275, + "description": "TV, movies, or the like, watched with family.", + "id": "e4hbzc6f3t43gfrbfj25m7ve", + "name": "Watching something", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 2.0, + "colorId": "wseq6pmjce562ds089pduv24", + "createdAt": 1732444277, + "description": "Time spent with friends.", + "id": "gozno3fockquztot23l7u62w", + "name": "Friends", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 22.0, + "colorId": "wseq6pmjce562ds089pduv24", + "createdAt": 1732444278, + "description": "Party with friends.", + "id": "on371jnjjof728m72hvmvvq5", + "name": "Party", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 26.0, + "colorId": "wseq6pmjce562ds089pduv24", + "createdAt": 1732444280, + "description": "Playing active games or sports with friends.", + "id": "uy1mexnou9phcm9usxujqcu6", + "name": "Active / Sports", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 28.0, + "colorId": "wseq6pmjce562ds089pduv24", + "createdAt": 1732444281, + "description": "Preparing or eating a meal with friends.", + "id": "mqamdgvll8fhpeh04iaahgt1", + "name": "Meal", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 29.0, + "colorId": "wseq6pmjce562ds089pduv24", + "createdAt": 1732444283, + "description": "Drugs with friends.", + "id": "jn5uzh2j2n4yj8faf1hq1m1h", + "name": "Drugs", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 3.0, + "colorId": "v86zsclb9cayijf8djilk5lv", + "createdAt": 1732444284, + "description": "Time spent on dates or primarily with/for Jen.", + "id": "na3g6sz33dxgxd2p27pep4d8", + "name": "Jen", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 31.0, + "colorId": "v86zsclb9cayijf8djilk5lv", + "createdAt": 1732444286, + "description": "Time spent with Jen's family.", + "id": "pw0ev83xwb7egvkjkwatjy0k", + "name": "Jen's family", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 32.0, + "colorId": "v86zsclb9cayijf8djilk5lv", + "createdAt": 1732444287, + "description": "Time with primarily Jen's friends.", + "id": "aybqbkj4oigocfhtgf9xiziu", + "name": "Jen's friends", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 33.0, + "colorId": "v86zsclb9cayijf8djilk5lv", + "createdAt": 1732444289, + "description": "Sex or sex-related activities.", + "id": "ocicgnndcpb2brdhi9hzj3jk", + "name": "Sex", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 34.0, + "colorId": "v86zsclb9cayijf8djilk5lv", + "createdAt": 1732444290, + "description": "Exploring and adventuring, with a more urban (non-nature) focus.", + "id": "z07iadfzm0gdnuexeybl78tj", + "name": "Exploring", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 35.0, + "colorId": "v86zsclb9cayijf8djilk5lv", + "createdAt": 1732444291, + "description": "When shit needs to get done, for me and Jen.", + "id": "ufdq763hkkv1a6fjjstsmkpg", + "name": "Errands & Logistics", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 36.0, + "colorId": "v86zsclb9cayijf8djilk5lv", + "createdAt": 1732444293, + "description": "Watching a movie, TV show, or Internet stuff with Jen.", + "id": "pnqrj2ksxd86g2kv3pwfokjj", + "name": "Watching something", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 37.0, + "colorId": "v86zsclb9cayijf8djilk5lv", + "createdAt": 1732444295, + "description": "Hiking, camping, and backcountry adventures with an active focus.", + "id": "pravf1muoqxvpg7pr590t882", + "name": "Hiking / Adventuring", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 38.0, + "colorId": "v86zsclb9cayijf8djilk5lv", + "createdAt": 1732444296, + "description": "Cooking, eating out, or just having a meal with my love.", + "id": "uoaghsua2r5qtp6ef0nwil01", + "name": "Meal with Jen", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 39.0, + "colorId": "v86zsclb9cayijf8djilk5lv", + "createdAt": 1732444297, + "description": "Rest and cuddles, other than primarily for sleep or sex.", + "id": "nb70nsg3e3ch27t1o3ke24av", + "name": "Relaxing / Napping", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 4.0, + "colorId": "wtlivlqr3ehecyo9h76asydc", + "createdAt": 1732444299, + "description": "Anything related to private flying.", + "id": "k5pxee2j5qr80fa844x28xyk", + "name": "Flying", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 41.0, + "colorId": "wtlivlqr3ehecyo9h76asydc", + "createdAt": 1732444300, + "description": "Dealing with FBOs, getting in or out of the plane, etc.", + "id": "qcg39xfln3r1a2ae9d1h6iyu", + "name": "Airports, etc.", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 42.0, + "colorId": "wtlivlqr3ehecyo9h76asydc", + "createdAt": 1732444302, + "description": "Tour or flight for fun with friends.", + "id": "gendguvt1pbmc9ybgrhrr8sa", + "name": "Flying with friends", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 43.0, + "colorId": "wtlivlqr3ehecyo9h76asydc", + "createdAt": 1732444303, + "description": "A flying trip primarily for me and Jen.", + "id": "lo5fsd43de6rkf3fia3xsjmu", + "name": "Flying with Jen", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 44.0, + "colorId": "wtlivlqr3ehecyo9h76asydc", + "createdAt": 1732444305, + "description": "Checking weather, planning, filing flight plan, etc.", + "id": "jepjh7y5y49c8f8araawnyzr", + "name": "Flight planning", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 45.0, + "colorId": "wtlivlqr3ehecyo9h76asydc", + "createdAt": 1732444306, + "description": "A primarily business-oriented flying trip.", + "id": "wm210bl3mdfxzwimxk0hrxj3", + "name": "Flying for work", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 46.0, + "colorId": "wtlivlqr3ehecyo9h76asydc", + "createdAt": 1732444307, + "description": "A primarily maintenance-related flying trip.", + "id": "dhjwzwur71t7y4nvil2f97l6", + "name": "Maintenance", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 47.0, + "colorId": "wtlivlqr3ehecyo9h76asydc", + "createdAt": 1732444309, + "description": "A primarily training-related flying trip.", + "id": "gmdiu72n8q9u5nc8273ej76c", + "name": "Training", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 49.0, + "colorId": "wtlivlqr3ehecyo9h76asydc", + "createdAt": 1732444310, + "description": "e.g., Phone calls, research, buying/selling", + "id": "lm2xe9ouzo07ewlfpiu3xl74", + "name": "Other Flying Related", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 5.0, + "colorId": "nnpn5rrn3wx47i1553tvx8tb", + "createdAt": 1732444312, + "description": "Time spent working for money.", + "id": "bwnzw4g3dm1qns7cw0h1gwt1", + "name": "Work", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 51.0, + "colorId": "nnpn5rrn3wx47i1553tvx8tb", + "createdAt": 1732444313, + "description": "Pitching, diligence, calls, or somehow convincing investors to pony up.", + "id": "k3eofqjexruoklmqys5pndq9", + "name": "Investors", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 52.0, + "colorId": "nnpn5rrn3wx47i1553tvx8tb", + "createdAt": 1732444315, + "description": "Meeting with one or more of Perumal, Bonney, TM.", + "id": "veb5xubue47hqhebyzt3jlaa", + "name": "Cofounders", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 53.0, + "colorId": "nnpn5rrn3wx47i1553tvx8tb", + "createdAt": 1732444316, + "description": "This was set up because I was working so hard on the wedding in 2023 I thought I was going to die, but it really doesn't have much use being in the 5s.", + "id": "saun6t9fo6js9vfg0jta6mcd", + "name": "[REMAP ME] Wedding prep", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 55.0, + "colorId": "nnpn5rrn3wx47i1553tvx8tb", + "createdAt": 1732444318, + "description": "Dealing with Board members or Board matters.", + "id": "lyad7iipkvwefn9xdd2mn48e", + "name": "Board", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 56.0, + "colorId": "nnpn5rrn3wx47i1553tvx8tb", + "createdAt": 1732444319, + "description": "Getting the inbox under control.", + "id": "zm2zwf7h29qz6py1i2fo225z", + "name": "Emails", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 57.0, + "colorId": "nnpn5rrn3wx47i1553tvx8tb", + "createdAt": 1732444321, + "description": "Thinking, ruminating, or journaling about work.", + "id": "gfxtjd5sae6d8i0y7rsmv3gv", + "name": "Work meditation", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 58.0, + "colorId": "nnpn5rrn3wx47i1553tvx8tb", + "createdAt": 1732444322, + "description": "Out building work-related relationships.", + "id": "gsbbgkm2um4vg30rs4dsmhlb", + "name": "Socializing", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 6.0, + "colorId": "y6hn5dkk8jfixg2nf11k5rvd", + "createdAt": 1732444323, + "description": "Time spent active (working out), fulfilling obligations, running errands, doing work without pay, or otherwise in a useful and productive way.", + "id": "gjfckt52lo0j5ucagp6r5ykp", + "name": "Productive", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 61.0, + "colorId": "y6hn5dkk8jfixg2nf11k5rvd", + "createdAt": 1732444325, + "description": "Tidying, organizing, or cleaning a space.", + "id": "cdk4v2mcer9cvtmsd51pwfc6", + "name": "Cleaning", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 62.0, + "colorId": "y6hn5dkk8jfixg2nf11k5rvd", + "createdAt": 1732444327, + "description": "Having important conversations or working on the big stuff.", + "id": "sn705vdodh4uy4lwxtua656o", + "name": "Relationship work", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 63.0, + "colorId": "y6hn5dkk8jfixg2nf11k5rvd", + "createdAt": 1732444329, + "description": "Productivity related to the home or household.", + "id": "zgxrgvqzy27kjdm3eoq2842b", + "name": "Home maintenance", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 65.0, + "colorId": "y6hn5dkk8jfixg2nf11k5rvd", + "createdAt": 1732444330, + "description": "Chores related to life administration and personal finances.", + "id": "l22sh0he7xj2rdlgb55l2iag", + "name": "Personal finances / Life admin", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 66.0, + "colorId": "y6hn5dkk8jfixg2nf11k5rvd", + "createdAt": 1732444332, + "description": "Walking, running, biking, or hiking outdoors.", + "id": "em9t97vltn1sdikap2okjiw6", + "name": "Outdoor exercise", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 67.0, + "colorId": "y6hn5dkk8jfixg2nf11k5rvd", + "createdAt": 1732444333, + "description": "Working on my fitness in a gym.", + "id": "vvs26xkohcnt8sroal5l0edc", + "name": "Gym", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 68.0, + "colorId": "y6hn5dkk8jfixg2nf11k5rvd", + "createdAt": 1732444335, + "description": "Working on myself.", + "id": "w2666f0qdo0fk0q9fjiglwbi", + "name": "Meditation / Therapy", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 69.0, + "colorId": "y6hn5dkk8jfixg2nf11k5rvd", + "createdAt": 1732444336, + "description": "Working on a needed but Kafkaesque nightmare chore.", + "id": "t9sxvt97kgkkfehxcuin1t4a", + "name": "Bureaucracy / Bullshit", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 7.0, + "colorId": "v0tzvsbxs1wq66123qy71r3o", + "createdAt": 1732444338, + "description": "Time used for personal development, improving skills, or what I consider to be productive hobbies.", + "id": "w42zyrh33ll3g3is563unhqx", + "name": "Hobbies & Skills", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 71.0, + "colorId": "v0tzvsbxs1wq66123qy71r3o", + "createdAt": 1732444339, + "description": "Learning, studying, reading, or practicing a foreign language.", + "id": "d03wwnvt9nnkeoz18ouxpdgo", + "name": "Language learning", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 72.0, + "colorId": "v0tzvsbxs1wq66123qy71r3o", + "createdAt": 1732444341, + "description": "Preparing food.", + "id": "grgvf8su5ms01tstj3jmcu2z", + "name": "Cooking", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 73.0, + "colorId": "v0tzvsbxs1wq66123qy71r3o", + "createdAt": 1732444342, + "description": "Fun or useful stuff for the house, including gardening, automation, or other projecs that are basically hobbies.", + "id": "adwj4mh49cqsj56muc1dhdal", + "name": "Home improvement", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 75.0, + "colorId": "v0tzvsbxs1wq66123qy71r3o", + "createdAt": 1732444344, + "description": "Reading an article, book, or something else edifying.", + "id": "csvjptm37vafhlp9vumc0vi0", + "name": "Reading", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 76.0, + "colorId": "v0tzvsbxs1wq66123qy71r3o", + "createdAt": 1732444345, + "description": "Working on a hobby or skill, but doing the boring / annoying bits.", + "id": "hzir2qmsy1fpww2fmi4ilxwf", + "name": "Organizing / Workflow", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 78.0, + "colorId": "v0tzvsbxs1wq66123qy71r3o", + "createdAt": 1732444347, + "description": "Nerding out and building something with computers.", + "id": "v4b02ymp61afkf6sxbi75a69", + "name": "Programming / Computers", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 79.0, + "colorId": "v0tzvsbxs1wq66123qy71r3o", + "createdAt": 1732444349, + "description": "Shopping that isn't a waste of time.", + "id": "gxel0vj9vdf9lxl4oz5k8bt6", + "name": "Shopping", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 8.0, + "colorId": "bmt6w43t0jd2s9x6igevhnmt", + "createdAt": 1732444350, + "description": "Time spent gaming, relaxing, consuming entertainment, or participating in passive activities.", + "id": "c5qffeg64wcsu4b2ow35pb5z", + "name": "Relaxation & Leisure", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 81.0, + "colorId": "bmt6w43t0jd2s9x6igevhnmt", + "createdAt": 1732444352, + "description": "Watching something produced for the screen (TV / Movies / Tiktok / YouTube)", + "id": "j7a54hdrm59e7h0kx4wjcu09", + "name": "Watching something", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 82.0, + "colorId": "bmt6w43t0jd2s9x6igevhnmt", + "createdAt": 1732444353, + "description": "Sweating or swimming in a relaxing way, other than to get clean.", + "id": "cauh6fv06sqj054idsc7os9e", + "name": "Hot tub / Sauna / Pool", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 83.0, + "colorId": "bmt6w43t0jd2s9x6igevhnmt", + "createdAt": 1732444355, + "description": "Self sex.", + "id": "cr1ddtrq7ut0wo8fgafcis1u", + "name": "Masturbation", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 87.0, + "colorId": "bmt6w43t0jd2s9x6igevhnmt", + "createdAt": 1732444356, + "description": "Exploring the inner cosmos.", + "id": "nal715gx0vw2jzrbi66keqpd", + "name": "Drugs", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 88.0, + "colorId": "bmt6w43t0jd2s9x6igevhnmt", + "createdAt": 1732444358, + "description": "High-octane relaxation.", + "id": "ool44khvisy404vzmkjo4bse", + "name": "Video games", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 89.0, + "colorId": "bmt6w43t0jd2s9x6igevhnmt", + "createdAt": 1732444359, + "description": "Scrolling.", + "id": "rz1a12cxs7oe097vdajtp462", + "name": "Social media", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 9.0, + "colorId": "lx36kwpg92wwtjfouxk5ha69", + "createdAt": 1732444361, + "description": "Time better spent doing something else.", + "id": "hsibd24jewb7rkyc68hvq2bo", + "name": "Waste", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 91.0, + "colorId": "lx36kwpg92wwtjfouxk5ha69", + "createdAt": 1732444362, + "description": "Literally staring at a clock with nothing else to do.", + "id": "zdxm0t6h86uw22mhecotcw1a", + "name": "Waiting / Killing time", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 93.0, + "colorId": "lx36kwpg92wwtjfouxk5ha69", + "createdAt": 1732444364, + "description": "Not understanding or communicating well.", + "id": "j5vt0dmzaomk859puypttx6f", + "name": "Fight", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 96.0, + "colorId": "lx36kwpg92wwtjfouxk5ha69", + "createdAt": 1732444365, + "description": "Something has gone horribly wrong, and everything else is canceled.", + "id": "i9q43q6ek9js8o5hok2b7ws1", + "name": "Disaster", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 97.0, + "colorId": "lx36kwpg92wwtjfouxk5ha69", + "createdAt": 1732444366, + "description": "Ah, mindless consumerism!", + "id": "nr9gqgr0seh6y8bvzm6zva1y", + "name": "Shopping", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 98.0, + "colorId": "lx36kwpg92wwtjfouxk5ha69", + "createdAt": 1732444368, + "description": "Lying in bed wishing I was asleep.", + "id": "jquru1u59f00gdapixmol2v6", + "name": "Can't sleep", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 99.0, + "colorId": "lx36kwpg92wwtjfouxk5ha69", + "createdAt": 1732444369, + "description": "Shoveling empty calories or toxins, knowing it's a mistake.", + "id": "z3gt25btq1ejib3ygv1aoq28", + "name": "Stress eating / drinking / smoking", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 10.0, + "colorId": "rk6d9osfs7hvjjofwh7a3tl9", + "createdAt": 1732444371, + "description": "Time spent for personal hygiene, getting around, or similar.", + "id": "z37g5owwy3jqlxv7ebvrl4qt", + "name": "Health & Travel", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 101.0, + "colorId": "rk6d9osfs7hvjjofwh7a3tl9", + "createdAt": 1732444372, + "description": "A regular ol' meal.", + "id": "h8xi5wefhl7gklh31j34d21l", + "name": "Food", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 103.0, + "colorId": "rk6d9osfs7hvjjofwh7a3tl9", + "createdAt": 1732444373, + "description": "Taking a bath.", + "id": "udix1ejz8zg7axmn56lh7tum", + "name": "Bath", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 104.0, + "colorId": "rk6d9osfs7hvjjofwh7a3tl9", + "createdAt": 1732444375, + "description": "Getting from a place to another place.", + "id": "bx9klccq3dxngm1ofq8lqggd", + "name": "Travel", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 105.0, + "colorId": "rk6d9osfs7hvjjofwh7a3tl9", + "createdAt": 1732444376, + "description": "Getting clean to go somewhere or do something -- or, flossing, brushing, and getting ready for bed..", + "id": "yjwfwupm3bzriffhumy3zdvt", + "name": "Bathroom", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 106.0, + "colorId": "rk6d9osfs7hvjjofwh7a3tl9", + "createdAt": 1732444378, + "description": "Packing for a trip and cleaning before or after a trip.", + "id": "r6288qk2icwl40anm9614dkx", + "name": "Packing & Cleaning", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 107.0, + "colorId": "rk6d9osfs7hvjjofwh7a3tl9", + "createdAt": 1732444379, + "description": "A doctor, dentist, hospital visit, or appointment of some kind.", + "id": "vn1xlghxpah2029ab1w15eua", + "name": "Medical / Health", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "code": 108.0, + "colorId": "rk6d9osfs7hvjjofwh7a3tl9", + "createdAt": 1732444380, + "description": "Taking care of myself in a more aesthetic or relaxing way.", + "id": "sk9z8c8uxw28lkqllx3n1e5h", + "name": "Grooming / Massage", + "parentId": null, + "userId": "n7yns1s5b122y5e8v0p6uyt8" + } +] \ No newline at end of file diff --git a/scripts/color.json b/scripts/color.json new file mode 100644 index 0000000..609a23a --- /dev/null +++ b/scripts/color.json @@ -0,0 +1,90 @@ +[ + { + "createdAt": 1732441996, + "hexcode": "#273036", + "id": "ej29qv1e7l1dnfxajl0561dt", + "inverse": "#ffffff", + "name": "Black", + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "createdAt": 1732441997, + "hexcode": "#00A9B3", + "id": "wtlivlqr3ehecyo9h76asydc", + "inverse": "#ffffff", + "name": "Blue", + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "createdAt": 1732441999, + "hexcode": "#BFFF55", + "id": "rk6d9osfs7hvjjofwh7a3tl9", + "inverse": "#000000", + "name": "Lime", + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "createdAt": 1732442000, + "hexcode": "#189749", + "id": "nnpn5rrn3wx47i1553tvx8tb", + "inverse": "#ffffff", + "name": "Green", + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "createdAt": 1732442002, + "hexcode": "#FF65AE", + "id": "v86zsclb9cayijf8djilk5lv", + "inverse": "#ffffff", + "name": "Pink", + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "createdAt": 1732442003, + "hexcode": "#5B3AB1", + "id": "bmt6w43t0jd2s9x6igevhnmt", + "inverse": "#ffffff", + "name": "Purple", + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "createdAt": 1732442004, + "hexcode": "#FF6D01", + "id": "y6hn5dkk8jfixg2nf11k5rvd", + "inverse": "#ffffff", + "name": "Orange", + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "createdAt": 1732442006, + "hexcode": "#FFF336", + "id": "v0tzvsbxs1wq66123qy71r3o", + "inverse": "#000000", + "name": "Yellow", + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "createdAt": 1732442007, + "hexcode": "#005744", + "id": "wseq6pmjce562ds089pduv24", + "inverse": "#ffffff", + "name": "Teal", + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "createdAt": 1732442008, + "hexcode": "#C71634", + "id": "ahapbp9ujym8lgf4ny02i26h", + "inverse": "#ffffff", + "name": "Crimson", + "userId": "n7yns1s5b122y5e8v0p6uyt8" + }, + { + "createdAt": 1732442010, + "hexcode": "#FF2816", + "id": "lx36kwpg92wwtjfouxk5ha69", + "inverse": "#ffffff", + "name": "Red", + "userId": "n7yns1s5b122y5e8v0p6uyt8" + } +] \ No newline at end of file diff --git a/scripts/user.json b/scripts/user.json new file mode 100644 index 0000000..7b783a6 --- /dev/null +++ b/scripts/user.json @@ -0,0 +1,11 @@ +[ + { + "email": "ryan@ryanpandya.com", + "emailVerified": null, + "id": "n7yns1s5b122y5e8v0p6uyt8", + "image": null, + "name": "Ryan Pandya", + "password": "$2a$10$ngv9752uxDT11hSPfdZmAe2D8VXLB9mcXkN7TRPI5GZQCuriIu1gC", + "role": "admin" + } +] \ No newline at end of file