Hour get and update works - and CLI for it

This commit is contained in:
Ryan Pandya 2024-11-26 13:54:41 -08:00
parent f2d1ecd097
commit 4d6840559d
24 changed files with 1080 additions and 160 deletions

View File

@ -18,6 +18,7 @@
"dependencies": {
"@date-fns/tz": "^1.2.0",
"@lifetracker/db": "workspace:*",
"@lifetracker/shared": "workspace:^",
"@lifetracker/trpc": "workspace:^",
"date-fns": "^4.1.0",
"dotenv": "^16.4.1",

View File

@ -10,6 +10,7 @@ import { Command } from "@commander-js/extra-typings";
import { getBorderCharacters, table } from "table";
import { format } from "date-fns";
import { TZDate } from "@date-fns/tz";
import { getHourFromTime, getTimeFromHour } from "@lifetracker/shared/utils/hours";
function moodToStars(mood: number) {
// const full_stars = Math.floor(mood / 2);
@ -41,17 +42,21 @@ export const daysCmd = new Command()
const moodStr = moodToStars(day.mood);
const dateStr = format(day.date, "EEEE, MMMM do");
const data: string[][] = [[dateStr, moodStr], [day.comment ?? "No comment", '',]];
const data: string[][] = [[dateStr, '', moodStr], [day.comment ?? "No comment", '', '']];
day.hours.forEach((h) => {
data.push([getHourFromTime(h.time), h.categoryCode ?? "--", h.comment ?? ""]);
})
console.log(table(data, {
// border: getBorderCharacters("ramac"),
// singleLine: true,
spanningCells: [{ col: 0, row: 1, colSpan: 2 }],
spanningCells: [{ col: 0, row: 1, colSpan: 3 }, { col: 1, row: 2, colSpan: 2 }],
drawVerticalLine: (lineIndex, columnCount) => {
return lineIndex === 0 || lineIndex === columnCount || (lineIndex === 0 && columnCount === 2);
},
drawHorizontalLine: (lineIndex, rowCount) => {
return (lineIndex < 2 || lineIndex === rowCount);
return (lineIndex < 2 || lineIndex === 2 || lineIndex === rowCount);
},
}));
}

View File

@ -0,0 +1,67 @@
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";
import { getHourFromTime, getTimeFromHour } from "@lifetracker/shared/utils/hours";
import { ZHour } from "@lifetracker/shared/types/days";
export const hoursCmd = new Command()
.name("hour")
.description("Get or set data for a specific hour")
.argument('<date>', 'A date in ISO-8601 format, or "yesterday", "today", "tomorrow", etc.')
.argument('<hour>', 'An hour between 0-23, or 1-12 [AM/PM]')
.argument('[code]', 'Optionally set the code for this hour')
.option('-c, --comment <comment>', "edit this hour's comment")
.action(async (dateQuery = "today", hour: string, code: string | undefined, flags?) => {
const api = getAPIClient();
let res: string | ZHour;
try {
const props = { dateQuery: dateQuery, time: getTimeFromHour(hour), code: code, ...flags };
if (code) {
// Update
console.log(props);
res = await api.hours.update.mutate({ ...props });
}
else {
// Get
res = await api.hours.get.query({ ...props });
}
}
catch (error) {
printErrorMessageWithReason("Failed to manipulate hour", error as object);
}
if (getGlobalOptions().json) {
printObject(res);
} else {
const data = [
['Doing:', res.categoryName ?? "Undefined"],
];
if (res.comment) {
data.push(['Comment:', res.comment ?? "Undefined"]);
}
console.log(table(data, {
// border: getBorderCharacters("ramac"),
// singleLine: true,
// spanningCells: [{ col: 0, row: 0, colSpan: 2 },],
header: { alignment: "center", content: `${format(res.date, "EEEE, MMMM dd")} at ${getHourFromTime(res.time, true)}` },
drawVerticalLine: (lineIndex, columnCount) => {
return lineIndex === 0 || lineIndex === columnCount || (lineIndex === 0 && columnCount === 2);
},
drawHorizontalLine: (lineIndex, rowCount) => {
return (lineIndex < 2 || lineIndex === 2 || lineIndex === rowCount);
},
}));
}
});

View File

@ -5,6 +5,7 @@ import { whoamiCmd } from "@/commands/whoami";
import { colorsCmd } from "@/commands/colors";
import { categoriesCmd } from "@/commands/categories";
import { daysCmd } from "@/commands/days";
import { hoursCmd } from "./commands/hours";
import { config } from "dotenv";
@ -40,6 +41,7 @@ program.addCommand(whoamiCmd);
program.addCommand(daysCmd);
program.addCommand(colorsCmd);
program.addCommand(categoriesCmd);
program.addCommand(hoursCmd);
setGlobalOptions(program.opts());

View File

@ -42,6 +42,11 @@ export default async function DayView({
<EditableDayComment day={day}
className="text-xl"
/>
<Separator />
{day.hours}
</div>
);
}

View File

@ -57,19 +57,21 @@ CREATE TABLE `day` (
`id` text PRIMARY KEY NOT NULL,
`date` text NOT NULL,
`mood` integer,
`comment` text
`comment` text,
`userId` text NOT NULL,
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `entry` (
CREATE TABLE `hour` (
`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,
`time` integer,
`dayId` text NOT NULL,
`categoryId` text,
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`dayId`) REFERENCES `day`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`categoryId`) REFERENCES `category`(`id`) ON UPDATE no action ON DELETE no action
);
--> statement-breakpoint
@ -102,5 +104,6 @@ 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 `hour_time_unique` ON `hour` (`time`);--> statement-breakpoint
CREATE UNIQUE INDEX `hour_dayId_time_unique` ON `hour` (`dayId`,`time`);--> statement-breakpoint
CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);

View File

@ -1,7 +1,7 @@
{
"version": "6",
"dialect": "sqlite",
"id": "ceed45ad-37d0-4e59-92bd-3be02d271535",
"id": "ac3ad6ee-ccd2-4f91-9be7-83bd5b1d9ec8",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"account": {
@ -434,6 +434,13 @@
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
@ -445,12 +452,26 @@
"isUnique": true
}
},
"foreignKeys": {},
"foreignKeys": {
"day_userId_user_id_fk": {
"name": "day_userId_user_id_fk",
"tableFrom": "day",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"entry": {
"name": "entry",
"hour": {
"name": "hour",
"columns": {
"id": {
"name": "id",
@ -480,23 +501,16 @@
"notNull": false,
"autoincrement": false
},
"dayId": {
"name": "dayId",
"time": {
"name": "time",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"startedAt": {
"name": "startedAt",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"endedAt": {
"name": "endedAt",
"type": "integer",
"dayId": {
"name": "dayId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
@ -505,25 +519,31 @@
"name": "categoryId",
"type": "text",
"primaryKey": false,
"notNull": true,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"entry_userId_startedAt_endedAt_unique": {
"name": "entry_userId_startedAt_endedAt_unique",
"hour_time_unique": {
"name": "hour_time_unique",
"columns": [
"userId",
"startedAt",
"endedAt"
"time"
],
"isUnique": true
},
"hour_dayId_time_unique": {
"name": "hour_dayId_time_unique",
"columns": [
"dayId",
"time"
],
"isUnique": true
}
},
"foreignKeys": {
"entry_userId_user_id_fk": {
"name": "entry_userId_user_id_fk",
"tableFrom": "entry",
"hour_userId_user_id_fk": {
"name": "hour_userId_user_id_fk",
"tableFrom": "hour",
"tableTo": "user",
"columnsFrom": [
"userId"
@ -534,9 +554,22 @@
"onDelete": "cascade",
"onUpdate": "no action"
},
"entry_categoryId_category_id_fk": {
"name": "entry_categoryId_category_id_fk",
"tableFrom": "entry",
"hour_dayId_day_id_fk": {
"name": "hour_dayId_day_id_fk",
"tableFrom": "hour",
"tableTo": "day",
"columnsFrom": [
"dayId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"hour_categoryId_category_id_fk": {
"name": "hour_categoryId_category_id_fk",
"tableFrom": "hour",
"tableTo": "category",
"columnsFrom": [
"categoryId"

View File

@ -5,8 +5,8 @@
{
"idx": 0,
"version": "6",
"when": 1732544381615,
"tag": "0000_powerful_zodiak",
"when": 1732648521848,
"tag": "0000_even_mysterio",
"breakpoints": true
}
]

View File

@ -7,7 +7,7 @@
"scripts": {
"dev": "drizzle-kit studio",
"generate": "drizzle-kit generate",
"reset": "tsc reset.ts",
"reset": "tsx reset.ts",
"typecheck": "tsc --noEmit",
"migrate": "tsx migrate.ts",
"studio": "drizzle-kit studio"
@ -26,7 +26,8 @@
"@lifetracker/typescript-config": "workspace:*",
"@tsconfig/node21": "^21.0.1",
"@types/better-sqlite3": "^7.6.9",
"drizzle-kit": "^0.24.2"
"drizzle-kit": "^0.24.2",
"sqlite3": "^5.1.7"
},
"eslintConfig": {
"root": true,

93
packages/db/reset.ts Normal file
View File

@ -0,0 +1,93 @@
import sqlite3 from 'sqlite3';
import fs from 'fs';
import path from 'path';
// Read JSON files
const users = JSON.parse(fs.readFileSync('./seed/user.json', 'utf-8'));
const apiKeys = JSON.parse(fs.readFileSync('./seed/apiKey.json', 'utf-8'));
const colors = JSON.parse(fs.readFileSync('./seed/color.json', 'utf-8'));
const categories = JSON.parse(fs.readFileSync('./seed/category.json', 'utf-8'));
// Connect to SQLite
const db = new sqlite3.Database('/home/ryan/Notes/lifetracker/lifetracker.db');
// Helper function to insert data
const insertData = (table, columns, values) => {
const placeholders = columns.map(() => '?').join(', ');
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${placeholders})`;
db.run(sql, values, (err) => {
if (err) {
console.error(`Error inserting into ${table}:`, err.message);
}
});
};
// Insert users
users.forEach(user => {
insertData(
'user',
['id', 'name', 'email', 'emailVerified', 'image', 'password', 'role'],
[
user.id,
user.name,
user.email,
user.emailVerified || null,
user.image || null,
user.password,
user.role || 'user'
]
);
});
// Insert API keys
apiKeys.forEach(apiKey => {
insertData(
'apiKey',
['id', 'keyHash', 'keyId', 'name', 'userId', 'createdAt'],
[
apiKey.id,
apiKey.keyHash,
apiKey.keyId,
apiKey.name,
apiKey.userId,
apiKey.createdAt || new Date().toISOString()
]
);
});
categories.forEach(category => {
insertData(
'category',
['id', 'code', 'colorId', 'createdAt', 'description', 'name', 'parentId', 'userId'],
[
category.id,
category.code,
category.colorId || null,
category.createdAt || new Date().toISOString(),
category.description || null,
category.name,
category.parentId || null,
category.userId
]
);
});
colors.forEach(color => {
insertData(
'color',
['id', 'name', 'hexcode', 'inverse', 'userId', 'createdAt'],
[
color.id,
color.name,
color.hexcode,
color.inverse || null,
color.userId,
color.createdAt || new Date().toISOString()
]
);
});
// Close the connection
db.close(() => {
console.log('Database population completed!');
});

View File

@ -130,8 +130,34 @@ export const days = sqliteTable("day", {
date: text("date").notNull().unique(),
mood: integer("mood"),
comment: text("comment"),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
});
export const hours = sqliteTable(
"hour",
{
id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => createId()),
createdAt: createdAtField(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
comment: text("comment"),
time: integer("time").unique(),
dayId: text("dayId").notNull().references(() => days.id),
categoryId: text("categoryId").references(() => categories.id),
},
(e) => ({
uniq: unique().on(e.dayId, e.time)
}),
)
export const colors = sqliteTable(
"color",
{
@ -179,27 +205,7 @@ 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
@ -212,7 +218,8 @@ export const apiKeyRelations = relations(apiKeys, ({ one }) => ({
export const userRelations = relations(users, ({ many }) => ({
categories: many(categories),
colors: many(colors),
entries: many(entries),
days: many(days),
hours: many(hours),
}));
@ -242,31 +249,35 @@ export const categoriesRelations = relations(
fields: [categories.parentId],
references: [categories.id],
}),
entries: many(entries),
hours: many(hours),
}),
);
export const daysRelations = relations(
days,
({ many }) => ({
entries: many(entries),
({ many, one }) => ({
user: one(users, {
fields: [days.userId],
references: [users.id],
}),
hours: many(hours),
}),
);
export const entriesRelations = relations(
entries,
export const hoursRelations = relations(
hours,
({ many, one }) => ({
user: one(users, {
fields: [entries.userId],
fields: [hours.userId],
references: [users.id],
}),
categories: one(categories, {
fields: [entries.categoryId],
fields: [hours.categoryId],
references: [categories.id],
}
),
day: one(days, {
fields: [entries.dayId],
fields: [hours.dayId],
references: [days.id],
}),
}),

View File

@ -5,6 +5,8 @@
"private": true,
"type": "module",
"dependencies": {
"@date-fns/tz": "^1.2.0",
"date-fns": "^4.1.0",
"winston": "^3.17.0",
"zod": "^3.23.8"
},

View File

@ -1,9 +1,24 @@
import { z } from "zod";
// Define the schema for the "hour" object
export const zHourSchema = z.object({
id: z.string().optional(),
dayId: z.string(),
date: z.string().optional(),
time: z.number(),
categoryCode: z.coerce.number().nullable(),
categoryId: z.string().nullable(),
categoryName: z.string().nullable(),
comment: z.string().nullable(),
});
export type ZHour = z.infer<typeof zHourSchema>;
export const zDaySchema = z.object({
id: z.string().optional(),
date: z.string(),
mood: z.number().nullable(),
comment: z.string().nullable(),
hours: z.array(zHourSchema),
});
export type ZDay = z.infer<typeof zDaySchema>;

View File

@ -0,0 +1,13 @@
import { format } from "date-fns";
import { TZDate } from "@date-fns/tz";
export function dateFromInput(input: { dateQuery: string }) {
let t: string;
if (input.dateQuery == "today") {
t = TZDate.tz("America/Los_Angeles");
}
else {
t = new TZDate(input.dateQuery, "Etc/UTC");
}
return format(t, "yyyy-MM-dd") + "T00:00:00";
}

View File

@ -0,0 +1,28 @@
export function getHourFromTime(time: number, includePeriod = false) {
const hour = time == 0 || time == 12 ? 12 : time % 12;
const period = time < 12 ? "AM" : "PM";
return includePeriod ? `${hour} ${period}` : `${hour}`;
}
export function getTimeFromHour(hour: string) {
const match = hour.match(/^(\d{1,2}) ?([aApP][mM])?$/);
if (!match) {
throw new Error("Invalid time format");
}
let time = parseInt(match[1]);
const period = match[2] ? match[2].toUpperCase() : null;
if (time > 12 || time < 1) {
throw new Error("Invalid hour");
}
if (period === 'PM' && time !== 12) {
time += 12;
} else if (period === 'AM' && time === 12) {
time = 0;
}
return time;
}

View File

@ -6,12 +6,14 @@ import { adminAppRouter } from "./admin";
import { categoriesAppRouter } from "./categories";
import { colorsAppRouter } from "./colors";
import { daysAppRouter } from "./days";
import { hoursAppRouter } from "./hours";
export const appRouter = router({
users: usersAppRouter,
apiKeys: apiKeysAppRouter,
admin: adminAppRouter,
days: daysAppRouter,
hours: hoursAppRouter,
colors: colorsAppRouter,
categories: categoriesAppRouter,
});

View File

@ -3,33 +3,22 @@ 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 { categories, days, hours, } 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;
if (input.dateQuery == "today") {
t = TZDate.tz("America/Los_Angeles");
}
else {
t = new TZDate(input.dateQuery, "Etc/UTC");
}
return format(t, "yyyy-MM-dd") + "T00:00:00";
}
import { dateFromInput } from "@lifetracker/shared/utils/days";
async function createDay(date: string, ctx: Context) {
return await ctx.db.transaction(async (trx) => {
try {
const result = await trx
// Create the Day object
const dayRes = await trx
.insert(days)
.values({
userId: ctx.user!.id,
date: date,
})
.returning({
@ -38,7 +27,20 @@ async function createDay(date: string, ctx: Context) {
mood: days.mood,
comment: days.comment,
});
return result[0];
const dayId = dayRes[0].id;
// Generate 24 "hour" objects
const hoursData = Array.from({ length: 24 }, (_, hour) => ({
userId: ctx.user!.id,
dayId: dayId,
time: hour
}));
// Insert the "hour" objects
await trx.insert(hours).values(hoursData);
return dayRes;
} catch (e) {
console.log(e);
if (e instanceof SqliteError) {
@ -66,7 +68,9 @@ export const daysAppRouter = router({
.query(async ({ input, ctx }) => {
const date = dateFromInput(input);
const res = await ctx.db
// Fetch the day data
let dayRes;
dayRes = await ctx.db
.select({
id: days.id,
date: days.date,
@ -76,8 +80,32 @@ export const daysAppRouter = router({
.from(days)
.where(eq(days.date, date));
const day = res.length == 0 ? createDay(date, ctx) : res[0];
return day;
if (dayRes.length === 0) {
dayRes = await createDay(date, ctx);
}
const day = dayRes[0];
// Fetch the hours data for the corresponding dayId
const hoursRes = await ctx.db
.select({
id: hours.id,
dayId: hours.dayId,
time: hours.time,
categoryId: hours.categoryId,
categoryCode: categories.code,
comment: hours.comment,
})
.from(hours)
.where(eq(hours.dayId, day.id))
.leftJoin(categories, eq(categories.id, hours.categoryId))
// Combine the day and hours data
const result = {
...day,
hours: hoursRes,
};
return result;
}),
update: authedProcedure
.input(

View File

@ -0,0 +1,111 @@
import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
import { and, desc, eq, inArray, notExists } from "drizzle-orm";
import { date, z } from "zod";
import { SqliteError } from "@lifetracker/db";
import { categories, days, hours, } from "@lifetracker/db/schema";
import {
zDaySchema, ZDay, ZHour, zHourSchema
} 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";
import { dateFromInput } from "@lifetracker/shared/utils/days";
export const hoursAppRouter = router({
get: authedProcedure
.input(z.object({
dateQuery: z.string(),
time: z.number()
}))
.output(zHourSchema)
.query(async ({ input, ctx }) => {
const date = dateFromInput(input);
console.log(input);
const hourRes = await ctx.db
.select({
id: hours.id,
dayId: hours.dayId,
time: hours.time,
categoryId: hours.categoryId,
categoryCode: categories.code,
categoryName: categories.name,
comment: hours.comment,
})
.from(hours)
.leftJoin(days, eq(days.id, hours.dayId)) // Ensure days table is joined first
.leftJoin(categories, eq(categories.id, hours.categoryId))
.where(and(eq(hours.time, input.time), eq(days.date, date))) // Use correct alias for days table
console.log(hourRes);
return {
date: format(date, "yyyy-MM-dd"),
...hourRes[0]
};
}),
update: authedProcedure
.input(
z.object({
dateQuery: z.string(),
time: z.number(),
code: z.string().optional(),
comment: z.string().nullable(),
}),
)
// .output(zHourSchema)
.mutation(async ({ input, ctx }) => {
const { dateQuery, time, code, ...updatedProps } = input;
var date = dateFromInput({ dateQuery: dateQuery });
const category = await ctx.db.select(
{
id: categories.id,
name: categories.name,
}
)
.from(categories)
.where(
and(
eq(categories.code, code),
eq(categories.userId, ctx.user!.id),
)
);
const day = await ctx.db.select(
{ id: days.id }
)
.from(days)
.where(
and(
eq(days.date, date),
eq(days.userId, ctx.user!.id),
)
);
const newProps = {
categoryId: category[0].id,
...updatedProps
};
if (newProps.comment == "") { newProps.comment = null }
const hourRes = await ctx.db
.update(hours)
.set(newProps)
.where(
and(
eq(hours.time, time),
eq(hours.dayId, day[0].id),
eq(hours.userId, ctx.user!.id)
)
)
.returning();
return {
date: format(date, "yyyy-MM-dd"),
categoryName: category[0].name,
...hourRes[0]
}
}),
});

543
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

93
scripts/create_user.sh Executable file
View File

@ -0,0 +1,93 @@
#!/usr/bin/env node
const sqlite3 = require('sqlite3').verbose();
const fs = require('fs');
// Read JSON files
const users = JSON.parse(fs.readFileSync('user.json'));
const apikeys = JSON.parse(fs.readFileSync('apikey.json'));
const colors = JSON.parse(fs.readFileSync('color.json'));
const categories = JSON.parse(fs.readFileSync('category.json'));
// Connect to SQLite
const db = new sqlite3.Database('/home/ryan/Notes/lifetracker/lifetracker.db');
// Helper function to insert data
const insertData = (table, columns, values) => {
const placeholders = columns.map(() => '?').join(', ');
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${placeholders})`;
db.run(sql, values, (err) => {
if (err) {
console.error(`Error inserting into ${table}:`, err.message);
}
});
};
// Insert users
users.forEach(user => {
insertData(
'user',
['id', 'name', 'email', 'emailVerified', 'image', 'password', 'role'],
[
user.id,
user.name,
user.email,
user.emailVerified || null,
user.image || null,
user.password,
user.role || 'user'
]
);
});
// Insert API keys
apiKeys.forEach(apiKey => {
insertData(
'apiKey',
['id', 'keyHash', 'keyId', 'name', 'userId', 'createdAt'],
[
apiKey.id,
apiKey.keyHash,
apiKey.keyId,
apiKey.name,
apiKey.userId,
apiKey.createdAt || new Date().toISOString()
]
);
});
categories.forEach(category => {
insertData(
'category',
['id', 'code', 'colorId', 'createdAt', 'description', 'name', 'parentId', 'userId'],
[
category.id,
category.code,
category.colorId || null,
category.createdAt || new Date().toISOString(),
category.description || null,
category.name,
category.parentId || null,
category.userId
]
);
});
colors.forEach(color => {
insertData(
'color',
['id', 'name', 'hexcode', 'inverse', 'userId', 'createdAt'],
[
color.id,
color.name,
color.hexcode,
color.inverse || null,
color.userId,
color.createdAt || new Date().toISOString()
]
);
});
// Close the connection
db.close(() => {
console.log('Database population completed!');
});