Switch to Postgres

This commit is contained in:
Ryan Pandya 2025-01-31 18:09:27 -08:00
parent 6e96eed3f1
commit b7031bbd9d
57 changed files with 7131 additions and 732 deletions

19
.direnv/bin/nix-direnv-reload Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -e
if [[ ! -d "/home/ryan/Documents/Code/lifetracker" ]]; then
echo "Cannot find source directory; Did you move it?"
echo "(Looking for "/home/ryan/Documents/Code/lifetracker")"
echo 'Cannot force reload with this script - use "direnv reload" manually and then try again'
exit 1
fi
# rebuild the cache forcefully
_nix_direnv_force_reload=1 direnv exec "/home/ryan/Documents/Code/lifetracker" true
# Update the mtime for .envrc.
# This will cause direnv to reload again - but without re-building.
touch "/home/ryan/Documents/Code/lifetracker/.envrc"
# Also update the timestamp of whatever profile_rc we have.
# This makes sure that we know we are up to date.
touch -r "/home/ryan/Documents/Code/lifetracker/.envrc" "/home/ryan/Documents/Code/lifetracker/.direnv"/*.rc

View File

@ -0,0 +1 @@
/nix/store/r3pr5wyvfdi4h1b3wja7jcdwqjrfk9px-source

View File

@ -0,0 +1 @@
/nix/store/xpkysrb9pn0yqrm1bkll4dr2gqahj43x-source

View File

@ -0,0 +1 @@
/nix/store/yj1wxm9hh8610iyzqnz75kvs6xl8j3my-source

View File

@ -0,0 +1 @@
/nix/store/yp0xr7fzkk7rjgp564pn28r7f2mpdcj1-source

View File

@ -1 +0,0 @@
flake-profile-2-link

View File

@ -1 +0,0 @@
/nix/store/w46qqqb484a76v5k0lzv524yp9s89vly-nix-shell-env

View File

@ -0,0 +1 @@
/nix/store/11hqs52hdjhxhcb8idddn312grl1hr5i-nix-shell-env

File diff suppressed because it is too large Load Diff

View File

@ -35,6 +35,7 @@ RUN pnpm turbo run build
FROM base AS runner FROM base AS runner
WORKDIR /app WORKDIR /app
RUN corepack enable
# Don't run production as root # Don't run production as root
RUN addgroup --system --gid 1001 nodejs RUN addgroup --system --gid 1001 nodejs
@ -47,5 +48,12 @@ COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
# Handle db migrations
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
COPY --from=builder --chown=nextjs:nodejs /app/packages/db ./packages/db
RUN ["pnpm", "db:generate"]
RUN ["pnpm", "db:migrate"]
EXPOSE 3000 EXPOSE 3000
CMD node apps/web/server.js CMD node apps/web/server.js

View File

@ -12188,7 +12188,7 @@ const timezones$1 = {
"America/Los_Angeles": "Pacific Time", "America/Los_Angeles": "Pacific Time",
// "Atlantic/Azores": "Azores", // "Atlantic/Azores": "Azores",
// "Atlantic/Cape_Verde": "Cape Verde Islands", // "Atlantic/Cape_Verde": "Cape Verde Islands",
// GMT: "UTC", "Etc/UTC": "UTC",
"Europe/London": "The UK", "Europe/London": "The UK",
"Europe/Dublin": "Ireland", "Europe/Dublin": "Ireland",
// "Europe/Lisbon": "Lisbon", // "Europe/Lisbon": "Lisbon",

View File

@ -2,17 +2,24 @@ import { NextRequest, NextResponse } from 'next/server';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import serverConfig from '@lifetracker/shared/config'; import serverConfig from '@lifetracker/shared/config';
import { getConnectionDetails } from '@lifetracker/db/drizzle';
import { pgDump } from 'pg-dump-restore';
const DB_PATH = path.join(serverConfig.dataDir, 'lifetracker.db');
const dbPath = path.join(serverConfig.dataDir, `lifetracker-${new Date().getTime()}.sql`);
const returnVal = (await pgDump(getConnectionDetails(), {
filePath: dbPath,
}));
export async function GET(req: NextRequest) { export async function GET(req: NextRequest) {
try { try {
// Read the production database file // Read the production database file
const dbFile = fs.readFileSync(DB_PATH); const dbFile = fs.readFileSync(dbPath);
return new NextResponse(dbFile, { return new NextResponse(dbFile, {
headers: { headers: {
'Content-Type': 'application/octet-stream', 'Content-Type': 'application/octet-stream',
'Content-Disposition': 'attachment; filename="lifetracker.db"' 'Content-Disposition': `attachment; filename="lifetracker-${new Date().getTime()}.sql"`
} }
}); });

View File

@ -2,10 +2,13 @@ import { NextRequest, NextResponse } from 'next/server';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import serverConfig from '@lifetracker/shared/config'; import serverConfig from '@lifetracker/shared/config';
import { pgDump, pgRestore } from 'pg-dump-restore';
import { db, getConnectionDetails } from '@lifetracker/db/drizzle';
import { sql } from 'drizzle-orm';
// Define paths for uploaded files and the production DB // Define paths for uploaded files and the production DB
const UPLOAD_DIR = path.join(process.cwd(), 'uploads'); const UPLOAD_DIR = path.join(process.cwd(), 'uploads');
const DB_PATH = path.join(serverConfig.dataDir, 'lifetracker.db'); const DB_PATH = path.join(serverConfig.dataDir, `lifetracker-${new Date().getTime()}.sql`);
// Ensure upload directory exists // Ensure upload directory exists
if (!fs.existsSync(UPLOAD_DIR)) { if (!fs.existsSync(UPLOAD_DIR)) {
@ -35,25 +38,31 @@ export async function POST(req: NextRequest) {
const fileContent = bodyBuffer.slice(start, end); const fileContent = bodyBuffer.slice(start, end);
// Save the uploaded file to the upload directory // Save the uploaded file to the upload directory
const uploadedFilePath = path.join(UPLOAD_DIR, 'uploaded.db'); const uploadedFilePath = path.join(UPLOAD_DIR, 'uploaded.sql');
fs.writeFileSync(uploadedFilePath, fileContent); fs.writeFileSync(uploadedFilePath, fileContent);
// Validate the uploaded file extension // Back up the existing production database
if (!uploadedFilePath.endsWith('.db')) { const backupPath = `${serverConfig.dataDir}/pg-backup-${new Date().getTime()}.sql`;
return NextResponse.json({ message: 'Invalid file type. Please upload a .db file.' }, { status: 400 }); const { stdout, stderr } = await pgDump(getConnectionDetails(), {
} filePath: `${backupPath}`,
});
// Backup the current production database console.log("Backed up existing data to ", backupPath);
const backupPath = path.join( console.log(stdout);
serverConfig.dataDir,
`backup_${Date.now()}.db`
);
if (fs.existsSync(DB_PATH)) {
fs.copyFileSync(DB_PATH, backupPath);
}
// Replace the production database with the uploaded file // If it's a SQL file, restore the production database from the uploaded file
fs.renameSync(uploadedFilePath, DB_PATH); if (uploadedFilePath.endsWith('.sql')) {
const { stdout: restoreStdout, stderr: restoreStderr } = await pgRestore(getConnectionDetails(), {
clean: true,
filePath: uploadedFilePath,
});
}
// If it ends in .db, assume it's a sqlite
else if (uploadedFilePath.endsWith('.db')) {
// TODO, if ever
return NextResponse.json({ message: 'Invalid file type. Please upload a .sql file.' }, { status: 400 });
}
else { return NextResponse.json({ message: 'Invalid file type. Please upload a .sql file.' }, { status: 400 }); }
return NextResponse.json({ message: 'Database uploaded and replaced successfully!' }, { status: 200 }); return NextResponse.json({ message: 'Database uploaded and replaced successfully!' }, { status: 200 });
} catch (error) { } catch (error) {

View File

@ -14,7 +14,7 @@ export default async function AdminPage() {
return ( return (
<> <>
<div className="rounded-md border bg-background p-4"> <div className="rounded-md border bg-background p-4">
<ServerStats /> {/* <ServerStats /> */}
{/* <AdminActions /> */} {/* <AdminActions /> */}
<Separator /> <Separator />
<DatabaseSettings /> <DatabaseSettings />

View File

@ -19,7 +19,7 @@ import {
import { getServerAuthSession } from "@/server/auth"; import { getServerAuthSession } from "@/server/auth";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { clientConfig } from "@lifetracker/shared/config"; import serverConfig, { clientConfig } from "@lifetracker/shared/config";
import { useTimezone } from "@lifetracker/shared-react/hooks/timezones"; import { useTimezone } from "@lifetracker/shared-react/hooks/timezones";
import { getUserLocalSettings } from "@/lib/userLocalSettings/userLocalSettings"; import { getUserLocalSettings } from "@/lib/userLocalSettings/userLocalSettings";

View File

@ -47,7 +47,7 @@ export default function DatabaseSettings() {
const dbData = await download.blob(); const dbData = await download.blob();
const formData = new FormData(); const formData = new FormData();
formData.append('sqliteFile', dbData, 'remoteDB-file.db'); formData.append('sqlFile', dbData, 'remoteDB-file.sql');
uploadDB(formData, setRemoteCopyStatus); uploadDB(formData, setRemoteCopyStatus);
// try { // try {
@ -71,12 +71,12 @@ export default function DatabaseSettings() {
return ( return (
<><div className="mt-8 mb-2 flex w-full flex-col sm:flex-row"> <><div className="mt-8 mb-2 flex w-full flex-col sm:flex-row">
<div className="mb-4 w-full text-lg font-medium sm:w-1/3"> <div className="mb-4 w-full text-lg font-medium sm:w-1/3">
Upload SQLite Database Upload Postgres SQL
</div> </div>
<div className="w-full"> <div className="w-full">
<div className="mb-2"> <div className="mb-2">
<form onSubmit={handleSubmit} encType="multipart/form-data"> <form onSubmit={handleSubmit} encType="multipart/form-data">
<input type="file" name="sqliteFile" accept=".db" required /> <input type="file" name="sqlFile" accept=".sql" required />
<button type="submit">Upload</button> <button type="submit">Upload</button>
</form> </form>
{uploadStatus && <p>{uploadStatus}</p>} {uploadStatus && <p>{uploadStatus}</p>}
@ -85,7 +85,7 @@ export default function DatabaseSettings() {
</div> </div>
<div className="mb-2 flex w-full flex-col sm:flex-row"> <div className="mb-2 flex w-full flex-col sm:flex-row">
<div className="mb-4 w-full text-lg font-medium sm:w-1/3"> <div className="mb-4 w-full text-lg font-medium sm:w-1/3">
Download SQLite Database Download Postgres SQL
</div> </div>
<div className="w-full"> <div className="w-full">
<div className="mb-2"> <div className="mb-2">

View File

@ -48,8 +48,8 @@ export default function AnalyticsView() {
} }
return [ return [
// spacetime.now().subtract(1, "week").toNativeDate(), // spacetime.now().subtract(1, "week").toNativeDate(),
spacetime.now(useTimezone()).toNativeDate(), parseISO(spacetime.now(useTimezone()).format("iso-short")),
spacetime.now(useTimezone()).toNativeDate() parseISO(spacetime.now(useTimezone()).format("iso-short")),
]; ];
})(); })();
@ -75,6 +75,12 @@ export default function AnalyticsView() {
timezone: useTimezone(), timezone: useTimezone(),
}).data ?? []; }).data ?? [];
const weightData = api.measurements.getTimeseries.useQuery({
dateRange,
timezone: useTimezone(),
metricName: "weight"
}).data ?? [];
return ( return (
<div className="px-4 flex flex-col gap-y-4"> <div className="px-4 flex flex-col gap-y-4">
<div className="flex justify-between"> <div className="flex justify-between">
@ -176,7 +182,27 @@ export default function AnalyticsView() {
} }
</div> </div>
</div> </div>
<div className="flex gap-4">
<h2>Weight</h2>
<div>
{
!weightData ? <LoadingSpinner /> :
<ul>
{weightData.map((measurement) => (
<li key={measurement.id}>
{measurement.value} {measurement.unit}
&nbsp;,&nbsp;
{
spacetime(measurement.datetime)
.goto(useTimezone())
.format("{iso-short} at {hour} {ampm}")
}
</li>
))}
</ul>
}
</div>
</div>
<div className="flex gap-4"> <div className="flex gap-4">
<h2 className="">Drugs</h2> <h2 className="">Drugs</h2>
<div> <div>
@ -192,6 +218,6 @@ export default function AnalyticsView() {
} }
</div> </div>
</div> </div>
</div> </div >
); );
} }

View File

@ -22,7 +22,7 @@ import Link from "next/link";
export default function CategoriesView() { export default function CategoriesView() {
const { data: session } = useSession(); const { data: session } = useSession();
const { data: categories } = api.categories.list.useQuery(); const { data: categories } = api.categories.list.useQuery();
const { data: categoryStats } = api.categories.categoryStats.useQuery(); // const { data: categoryStats } = api.categories.categoryStats.useQuery();
const invalidateCategoryList = api.useUtils().categories.list.invalidate; const invalidateCategoryList = api.useUtils().categories.list.invalidate;
const { mutate: deleteCategory, isPending: isDeletionPending } = const { mutate: deleteCategory, isPending: isDeletionPending } =
@ -57,7 +57,7 @@ export default function CategoriesView() {
<TableCell className="py-1">{c.name}</TableCell> <TableCell className="py-1">{c.name}</TableCell>
<TableCell className="py-1">{c.description}</TableCell> <TableCell className="py-1">{c.description}</TableCell>
<TableCell className="py-1"> <TableCell className="py-1">
{categoryStats[c.id].numEntries} categoryStats[c.id].numEntries
</TableCell> </TableCell>
<TableCell className="flex gap-1 py-1"> <TableCell className="flex gap-1 py-1">
<ActionButtonWithTooltip <ActionButtonWithTooltip

View File

@ -103,11 +103,8 @@ export default function EditableHour({
const localDateTime = (h: ZHour): string => { const localDateTime = (h: ZHour): string => {
return spacetime(h.date).add(h.time + tzOffset, "hour").format('{hour} {ampm}'); return spacetime(h.datetime).format('{hour} {ampm}');
} }
hour.datetime = localDateTime(hour);
useEffect(() => { useEffect(() => {
// console.log(hour.categoryDesc); // console.log(hour.categoryDesc);
@ -133,12 +130,12 @@ export default function EditableHour({
{hourGroup.length > 1 {hourGroup.length > 1
? ( ? (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<span>{hour.datetime}</span> <span>{format(hour.datetime, "hh")}</span>
<span>|</span> <span>|</span>
<span>{localDateTime(hourGroup[hourGroup.length - 1])}</span> <span>{localDateTime(hourGroup[hourGroup.length - 1])}</span>
</div> </div>
) )
: <div className="flex flex-col items-center">{hour.datetime}</div> : <div className="flex flex-col items-center">{spacetime(hour.datetime).format('{hour} {ampm}')}</div>
} }
</span> </span>
<div className="flex justify-center"> <div className="flex justify-center">

View File

@ -6,7 +6,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { TRPCClientError } from "@trpc/client"; import { TRPCClientError } from "@trpc/client";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
import { zMeasurementSchema, ZMetric } from "@lifetracker/shared/types/metrics"; import { ZMeasurement, zMeasurementSchema, ZMetric } from "@lifetracker/shared/types/metrics";
import { ZHour } from "@lifetracker/shared/types/days"; import { ZHour } from "@lifetracker/shared/types/days";
import { Icon } from "@/components/ui/icon"; import { Icon } from "@/components/ui/icon";
@ -37,7 +37,7 @@ export default function HourMeasurementsDialog({
}) { }) {
const [hour, setHour] = useState(initialHour); const [hour, setHour] = useState(initialHour);
const [isOpen, onOpenChange] = useState(false); const [isOpen, onOpenChange] = useState(false);
const [pendingMeasurement, setPendingMeasurement] = useState(false); const [pendingMeasurement, setPendingMeasurement] = useState<Boolean | ZMeasurement>(false);
const pendingRef = useRef<HTMLInputElement>(null); const pendingRef = useRef<HTMLInputElement>(null);
useEffect(() => { useEffect(() => {

View File

@ -78,6 +78,7 @@
"next-auth": "^4.24.5", "next-auth": "^4.24.5",
"next-pwa": "^5.6.0", "next-pwa": "^5.6.0",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"pg-dump-restore": "^1.0.12",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"react": "^18.2.0", "react": "^18.2.0",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -22,8 +22,7 @@
emacs-nox emacs-nox
nodejs nodejs
pnpm pnpm
sqlite postgresql
] ++ optional stdenv.isLinux inotify-tools ] ++ optional stdenv.isLinux inotify-tools
++ optional stdenv.isDarwin terminal-notifier ++ optional stdenv.isDarwin terminal-notifier
++ optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [ ++ optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [

View File

@ -12,6 +12,7 @@
}, },
"devDependencies": { "devDependencies": {
"prettier": "^3.3.3", "prettier": "^3.3.3",
"tsx": "^4.19.2",
"turbo": "^2.2.3", "turbo": "^2.2.3",
"typescript": "^5.6.3" "typescript": "^5.6.3"
}, },

View File

@ -0,0 +1,16 @@
import "dotenv/config";
import { drizzle } from "drizzle-orm/better-sqlite3";
import Database from "better-sqlite3";
import * as schema from "./schema";
import serverConfig from "@lifetracker/shared/config";
export const databaseUrl = serverConfig.dataDir
? `${serverConfig.dataDir}/lifetracker.db`
: "./lifetracker.db";
const sqlite = new Database(databaseUrl);
export const db = drizzle(sqlite, {
schema,
logger: false,
});

View File

@ -1,17 +1,17 @@
import dotenv from "dotenv";
import type { Config } from "drizzle-kit"; import type { Config } from "drizzle-kit";
import serverConfig from "@lifetracker/shared/config"; import serverConfig from "@lifetracker/shared/config";
const databaseURL = serverConfig.dataDir const dbInfo = {
? `${serverConfig.dataDir}/lifetracker.db`
: "./lifetracker.db";
export default {
dialect: "sqlite",
schema: "./schema.ts", schema: "./schema.ts",
out: "./migrations", out: "./migrations",
dialect: "postgresql",
dbCredentials: { dbCredentials: {
url: databaseURL, url: process.env.NODE_ENV === "test"
}, ? serverConfig.testDatabaseUrl
} satisfies Config; : serverConfig.databaseUrl,
}
} satisfies Config;
export default dbInfo;

View File

@ -1,23 +1,34 @@
import "dotenv/config"; import "dotenv/config";
import { drizzle } from "drizzle-orm/better-sqlite3"; import { drizzle } from "drizzle-orm/node-postgres";
import Database from "better-sqlite3"; import { Pool } from "pg";
import * as schema from "./schema"; import * as schema from "./schema";
import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import serverConfig from "@lifetracker/shared/config";
import path from "path";
import dbConfig from "./drizzle.config"; const pool = new Pool({
connectionString: serverConfig.databaseUrl,
});
const sqlite = new Database(dbConfig.dbCredentials.url); export const db = drizzle(pool, {
export const db = drizzle(sqlite, {
schema, schema,
logger: false, logger: false,
}); });
export function getInMemoryDB(runMigrations: boolean) {
const mem = new Database(":memory:"); export function getConnectionDetails() {
const db = drizzle(mem, { schema, logger: true }); const sourceDb = serverConfig.databaseUrl
if (runMigrations) { const sourceDbUrl = new URL(sourceDb)
migrate(db, { migrationsFolder: path.resolve(__dirname, "./migrations") }); return {
port: parseInt(sourceDbUrl.port),
host: sourceDbUrl.hostname,
database: sourceDbUrl.pathname.slice(1),
username: sourceDbUrl.username,
password: sourceDbUrl.password
} }
return db; }
// Testing utility
export async function getTestDB() {
const testPool = new Pool({
connectionString: serverConfig.testDatabaseUrl,
});
return drizzle(testPool, { schema });
} }

View File

@ -1,17 +1,24 @@
import Database from "better-sqlite3"; import { Pool, QueryResult } from "pg";
import { ExtractTablesWithRelations } from "drizzle-orm"; import { ExtractTablesWithRelations } from "drizzle-orm";
import { SQLiteTransaction } from "drizzle-orm/sqlite-core"; import { PgTransaction } from "drizzle-orm/pg-core";
import * as schema from "./schema"; import * as schema from "./schema";
// Export the db instance from your drizzle setup (make sure your drizzle file is updated for Postgres)
export { db } from "./drizzle"; export { db } from "./drizzle";
export * as schema from "./schema"; export * as schema from "./schema";
export { SqliteError } from "better-sqlite3";
// This is exported here to avoid leaking better-sqlite types outside of this package. // If you need to export Postgres errors, you can export them from the pg package.
export type LifetrackerDBTransaction = SQLiteTransaction< // (Optional remove if not needed)
"sync", export { DatabaseError } from "pg";
Database.RunResult, export const ErrorCodes = {
typeof schema, UNIQUE_VIOLATION: "23505",
ExtractTablesWithRelations<typeof schema> };
>;
/**
* This type alias is provided to avoid leaking pg types outside of this package.
*
* Note:
* - The first generic parameter is set to "async" because most PostgreSQL drivers (like pg) are asynchronous.
* - The second generic parameter uses pg's QueryResult type.
*/
export type LifetrackerDBTransaction = PgTransaction<any, ExtractTablesWithRelations<typeof schema>>;

Binary file not shown.

View File

@ -0,0 +1,63 @@
// migrate-to-postgres.ts
import { sql } from "drizzle-orm";
import { db as pgDb, getConnectionDetails } from "./drizzle";
import { db as sqliteDb, databaseUrl } from "./drizzle-sqlite";
import * as schema from "./schema";
import { pgDump, pgRestore } from "pg-dump-restore";
import serverConfig from "@lifetracker/shared/config";
export async function migrateData() {
// First back up existing pg data
const filename = `${serverConfig.dataDir}/pg-backup-${new Date().getTime()}.sql`;
const { stdout, stderr } = await pgDump(getConnectionDetails(), {
filePath: `${filename}`,
});
console.log("Backed up existing data to ", filename);
console.log(stdout);
console.log(`Using sqlite db: ${databaseUrl}`);
const tables = {
"users": schema.users,
// "apiKeys": schema.apiKeys,
// "colors": schema.colors,
// "categories": schema.categories,
// "days": schema.days,
// "hours": schema.hours,
// "metrics": schema.metrics,
// "measurements": schema.measurements,
};
// console.log(await sqliteDb.select().from(schema.apiKeys));
// For each table in your schema, transfer the data
for (const tableName of Object.keys(tables)) {
const oldData = await sqliteDb.query[tableName].findMany();
console.log(`Table: ${tableName}`);
var skippedRecords = 0;
var migratedRecords = 0;
for (const record of oldData) {
if ((await pgDb.select().from(tables[tableName]).where(sql`${tables[tableName].id} = ${record.id}`)).length == 0) {
await pgDb.insert(tables[tableName]).values({
...record,
createdAt: new Date(),
});
migratedRecords++;
}
else {
skippedRecords++;
}
}
console.log(`Migrated ${migratedRecords} records (skipped ${skippedRecords}) from ${tableName}\n`);
}
console.log("Migration complete!");
}
migrateData().catch(console.error);

View File

@ -1,4 +1,17 @@
import { db } from "./drizzle"; import { db } from "./drizzle";
import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { migrate } from "drizzle-orm/node-postgres/migrator";
migrate(db, { migrationsFolder: "./migrations" }); async function runMigrations() {
try {
console.log('Starting database migrations...');
await migrate(db, { migrationsFolder: "./migrations" });
console.log('Migrations completed successfully');
} catch (error) {
console.error('Error during migration:', error);
process.exit(1);
} finally {
process.exit(0);
}
}
runMigrations();

View File

@ -1,137 +0,0 @@
CREATE TABLE `account` (
`userId` text NOT NULL,
`type` text NOT NULL,
`provider` text NOT NULL,
`providerAccountId` text NOT NULL,
`refresh_token` text,
`access_token` text,
`expires_at` integer,
`token_type` text,
`scope` text,
`id_token` text,
`session_state` text,
PRIMARY KEY(`provider`, `providerAccountId`),
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `apiKey` (
`id` text PRIMARY KEY NOT NULL,
`name` text NOT NULL,
`createdAt` integer NOT NULL,
`keyId` text NOT NULL,
`keyHash` text NOT NULL,
`userId` text NOT NULL,
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `category` (
`id` text PRIMARY KEY NOT NULL,
`createdAt` integer NOT NULL,
`name` text NOT NULL,
`code` real NOT NULL,
`description` text,
`colorId` text NOT NULL,
`parentId` text,
`userId` text NOT NULL,
FOREIGN KEY (`colorId`) REFERENCES `color`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`parentId`) REFERENCES `category`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `color` (
`id` text PRIMARY KEY NOT NULL,
`createdAt` integer NOT NULL,
`name` text NOT NULL,
`hexcode` text NOT NULL,
`inverse` text,
`userId` text NOT NULL,
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `config` (
`key` text PRIMARY KEY NOT NULL,
`value` text NOT NULL
);
--> statement-breakpoint
CREATE TABLE `day` (
`id` text PRIMARY KEY NOT NULL,
`date` text NOT NULL,
`mood` integer,
`comment` text,
`userId` text NOT NULL,
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `hour` (
`id` text PRIMARY KEY NOT NULL,
`createdAt` integer NOT NULL,
`userId` text NOT NULL,
`comment` text,
`time` integer NOT NULL,
`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 cascade,
FOREIGN KEY (`categoryId`) REFERENCES `category`(`id`) ON UPDATE no action ON DELETE no action
);
--> statement-breakpoint
CREATE TABLE `measurement` (
`id` text PRIMARY KEY NOT NULL,
`hourId` text,
`dayId` text NOT NULL,
`metricId` text NOT NULL,
`createdAt` integer NOT NULL,
`userId` text NOT NULL,
`value` text NOT NULL,
FOREIGN KEY (`hourId`) REFERENCES `hour`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`dayId`) REFERENCES `day`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`metricId`) REFERENCES `metric`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `metric` (
`id` text PRIMARY KEY NOT NULL,
`name` text NOT NULL,
`description` text,
`userId` text NOT NULL,
`type` text NOT NULL,
`unit` text,
`goal` real,
`icon` text NOT NULL,
`createdAt` integer NOT NULL,
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `session` (
`sessionToken` text PRIMARY KEY NOT NULL,
`userId` text NOT NULL,
`expires` integer NOT NULL,
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `user` (
`id` text PRIMARY KEY NOT NULL,
`name` text NOT NULL,
`email` text NOT NULL,
`emailVerified` integer,
`image` text,
`password` text,
`role` text DEFAULT 'user',
`timezone` text DEFAULT 'America/Los_Angeles' NOT NULL
);
--> statement-breakpoint
CREATE TABLE `verificationToken` (
`identifier` text NOT NULL,
`token` text NOT NULL,
`expires` integer NOT NULL,
PRIMARY KEY(`identifier`, `token`)
);
--> statement-breakpoint
CREATE UNIQUE INDEX `apiKey_keyId_unique` ON `apiKey` (`keyId`);--> statement-breakpoint
CREATE UNIQUE INDEX `apiKey_name_userId_unique` ON `apiKey` (`name`,`userId`);--> statement-breakpoint
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 `hour_dayId_time_unique` ON `hour` (`dayId`,`time`);--> statement-breakpoint
CREATE UNIQUE INDEX `metric_userId_name_unique` ON `metric` (`userId`,`name`);--> statement-breakpoint
CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);

View File

@ -0,0 +1,215 @@
CREATE TABLE IF NOT EXISTS "account" (
"userId" text NOT NULL,
"type" text NOT NULL,
"provider" text NOT NULL,
"providerAccountId" text NOT NULL,
"refresh_token" text,
"access_token" text,
"expires_at" integer,
"token_type" text,
"scope" text,
"id_token" text,
"session_state" text,
CONSTRAINT "account_provider_providerAccountId_pk" PRIMARY KEY("provider","providerAccountId")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "apiKey" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"keyId" text NOT NULL,
"keyHash" text NOT NULL,
"userId" text NOT NULL,
CONSTRAINT "apiKey_keyId_unique" UNIQUE("keyId"),
CONSTRAINT "apiKey_name_userId_unique" UNIQUE("name","userId")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "category" (
"id" text PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"name" text NOT NULL,
"code" real NOT NULL,
"description" text,
"colorId" text NOT NULL,
"parentId" text,
"userId" text NOT NULL,
CONSTRAINT "category_userId_code_unique" UNIQUE("userId","code")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "color" (
"id" text PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"name" text NOT NULL,
"hexcode" text NOT NULL,
"inverse" text,
"userId" text NOT NULL,
CONSTRAINT "color_userId_name_unique" UNIQUE("userId","name")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "config" (
"key" text PRIMARY KEY NOT NULL,
"value" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "day" (
"id" text PRIMARY KEY NOT NULL,
"date" text NOT NULL,
"mood" integer,
"comment" text,
"userId" text NOT NULL,
CONSTRAINT "day_date_unique" UNIQUE("date")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "hour" (
"id" text PRIMARY KEY NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"userId" text NOT NULL,
"comment" text,
"time" integer NOT NULL,
"dayId" text NOT NULL,
"categoryId" text
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "measurement" (
"id" text PRIMARY KEY NOT NULL,
"hourId" text,
"dayId" text NOT NULL,
"metricId" text NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"userId" text NOT NULL,
"value" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "metric" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"description" text,
"userId" text NOT NULL,
"type" text NOT NULL,
"unit" text,
"goal" real,
"icon" text NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "metric_userId_name_unique" UNIQUE("userId","name")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "session" (
"sessionToken" text PRIMARY KEY NOT NULL,
"userId" text NOT NULL,
"expires" timestamp NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "user" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"email" text NOT NULL,
"emailVerified" integer,
"image" text,
"password" text,
"role" text DEFAULT 'user',
"timezone" text DEFAULT 'America/Los_Angeles' NOT NULL,
CONSTRAINT "user_email_unique" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "verificationToken" (
"identifier" text NOT NULL,
"token" text NOT NULL,
"expires" timestamp NOT NULL,
CONSTRAINT "verificationToken_identifier_token_pk" PRIMARY KEY("identifier","token")
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "account" ADD CONSTRAINT "account_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "apiKey" ADD CONSTRAINT "apiKey_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "category" ADD CONSTRAINT "category_colorId_color_id_fk" FOREIGN KEY ("colorId") REFERENCES "public"."color"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "category" ADD CONSTRAINT "category_parentId_category_id_fk" FOREIGN KEY ("parentId") REFERENCES "public"."category"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "category" ADD CONSTRAINT "category_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "color" ADD CONSTRAINT "color_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "day" ADD CONSTRAINT "day_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "hour" ADD CONSTRAINT "hour_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "hour" ADD CONSTRAINT "hour_dayId_day_id_fk" FOREIGN KEY ("dayId") REFERENCES "public"."day"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "hour" ADD CONSTRAINT "hour_categoryId_category_id_fk" FOREIGN KEY ("categoryId") REFERENCES "public"."category"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "measurement" ADD CONSTRAINT "measurement_hourId_hour_id_fk" FOREIGN KEY ("hourId") REFERENCES "public"."hour"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "measurement" ADD CONSTRAINT "measurement_dayId_day_id_fk" FOREIGN KEY ("dayId") REFERENCES "public"."day"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "measurement" ADD CONSTRAINT "measurement_metricId_metric_id_fk" FOREIGN KEY ("metricId") REFERENCES "public"."metric"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "measurement" ADD CONSTRAINT "measurement_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "metric" ADD CONSTRAINT "metric_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "session" ADD CONSTRAINT "session_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@ -0,0 +1,18 @@
-- Custom SQL migration file, put you code below! --
-- Step 1: Add the new 'datetime' column
ALTER TABLE "hour"
ADD COLUMN "datetime" TIMESTAMPTZ;
-- Step 2: Update 'datetime' by combining 'Day.date' and 'Hour.time'
UPDATE "hour"
SET "datetime" = ("day"."date"::timestamp + ("hour"."time" * INTERVAL '1 hour')) AT TIME ZONE 'UTC'
FROM "day"
WHERE "hour"."dayId" = "day"."id";
-- Step 3: Add a unique constraint to 'datetime'
ALTER TABLE "hour"
ADD CONSTRAINT "hour_datetime_unique" UNIQUE("datetime");
-- Step 3: Drop the old 'time' column
ALTER TABLE "hour"
DROP COLUMN "time";

View File

@ -0,0 +1,54 @@
INSERT INTO "user" (
"id",
"name",
"email",
"emailVerified",
"image",
"password",
"role",
"timezone"
)
VALUES (
'n7yns1s5b122y5e8v0p6uyt8',
'Ryan Pandya',
'ryan@ryanpandya.com',
null,
null,
'$2a$10$ngv9752uxDT11hSPfdZmAe2D8VXLB9mcXkN7TRPI5GZQCuriIu1gC',
'admin',
'America/Los_Angeles'
);
INSERT INTO "apiKey" (
"id",
"name",
"createdAt",
"keyId",
"keyHash",
"userId"
)
VALUES (
'wxl62dg2n721tgzlz3spnjbu',
'CLI App',
to_timestamp('1731634820') AT TIME ZONE 'UTC',
'b9b17eb909ce0ecdbf33',
'$2a$10$NhOG42FjMbDycWHcJI4LH.Jp.aCV.op7llIP0zw/CBUmB3lO0HHQu',
'n7yns1s5b122y5e8v0p6uyt8'
);
INSERT INTO "apiKey" (
"id",
"name",
"createdAt",
"keyId",
"keyHash",
"userId"
)
VALUES (
'i703hxfm1xoov08jc214jnoz',
'Node-Red',
to_timestamp('1737068601') AT TIME ZONE 'UTC',
'820b8395dee76268b559',
'$2a$10$eUs4vYj2jr47LY6Rou4peudddpB/pyHRo2.cbomoUqNbAJf9RtZDS',
'n7yns1s5b122y5e8v0p6uyt8'
);

View File

@ -0,0 +1,6 @@
ALTER TABLE "apiKey" ALTER COLUMN "createdAt" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
ALTER TABLE "category" ALTER COLUMN "createdAt" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
ALTER TABLE "color" ALTER COLUMN "createdAt" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
ALTER TABLE "hour" ALTER COLUMN "createdAt" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
ALTER TABLE "measurement" ALTER COLUMN "createdAt" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
ALTER TABLE "metric" ALTER COLUMN "createdAt" SET DATA TYPE timestamp with time zone;

View File

@ -0,0 +1 @@
ALTER TABLE "hour" DROP COLUMN IF EXISTS "time";

View File

@ -1,88 +1,78 @@
{ {
"version": "6", "id": "8809abd6-6301-4dba-9f67-13071abd3086",
"dialect": "sqlite",
"id": "9a832735-dcfa-42cc-ad15-6be5b898a160",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": { "tables": {
"account": { "public.account": {
"name": "account", "name": "account",
"schema": "",
"columns": { "columns": {
"userId": { "userId": {
"name": "userId", "name": "userId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"type": { "type": {
"name": "type", "name": "type",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"provider": { "provider": {
"name": "provider", "name": "provider",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"providerAccountId": { "providerAccountId": {
"name": "providerAccountId", "name": "providerAccountId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"refresh_token": { "refresh_token": {
"name": "refresh_token", "name": "refresh_token",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"access_token": { "access_token": {
"name": "access_token", "name": "access_token",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"expires_at": { "expires_at": {
"name": "expires_at", "name": "expires_at",
"type": "integer", "type": "integer",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"token_type": { "token_type": {
"name": "token_type", "name": "token_type",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"scope": { "scope": {
"name": "scope", "name": "scope",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"id_token": { "id_token": {
"name": "id_token", "name": "id_token",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"session_state": { "session_state": {
"name": "session_state", "name": "session_state",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
} }
}, },
"indexes": {}, "indexes": {},
@ -103,78 +93,58 @@
}, },
"compositePrimaryKeys": { "compositePrimaryKeys": {
"account_provider_providerAccountId_pk": { "account_provider_providerAccountId_pk": {
"name": "account_provider_providerAccountId_pk",
"columns": [ "columns": [
"provider", "provider",
"providerAccountId" "providerAccountId"
], ]
"name": "account_provider_providerAccountId_pk"
} }
}, },
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"apiKey": { "public.apiKey": {
"name": "apiKey", "name": "apiKey",
"schema": "",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"name": { "name": {
"name": "name", "name": "name",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"createdAt": { "createdAt": {
"name": "createdAt", "name": "createdAt",
"type": "integer", "type": "timestamp",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "default": "now()"
}, },
"keyId": { "keyId": {
"name": "keyId", "name": "keyId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"keyHash": { "keyHash": {
"name": "keyHash", "name": "keyHash",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"userId": { "userId": {
"name": "userId", "name": "userId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "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
} }
}, },
"indexes": {},
"foreignKeys": { "foreignKeys": {
"apiKey_userId_user_id_fk": { "apiKey_userId_user_id_fk": {
"name": "apiKey_userId_user_id_fk", "name": "apiKey_userId_user_id_fk",
@ -191,78 +161,79 @@
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {
"apiKey_keyId_unique": {
"name": "apiKey_keyId_unique",
"nullsNotDistinct": false,
"columns": [
"keyId"
]
},
"apiKey_name_userId_unique": {
"name": "apiKey_name_userId_unique",
"nullsNotDistinct": false,
"columns": [
"name",
"userId"
]
}
}
}, },
"category": { "public.category": {
"name": "category", "name": "category",
"schema": "",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"createdAt": { "createdAt": {
"name": "createdAt", "name": "createdAt",
"type": "integer", "type": "timestamp",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "default": "now()"
}, },
"name": { "name": {
"name": "name", "name": "name",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"code": { "code": {
"name": "code", "name": "code",
"type": "real", "type": "real",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"description": { "description": {
"name": "description", "name": "description",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"colorId": { "colorId": {
"name": "colorId", "name": "colorId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"parentId": { "parentId": {
"name": "parentId", "name": "parentId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"userId": { "userId": {
"name": "userId", "name": "userId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}
},
"indexes": {
"category_userId_code_unique": {
"name": "category_userId_code_unique",
"columns": [
"userId",
"code"
],
"isUnique": true
} }
}, },
"indexes": {},
"foreignKeys": { "foreignKeys": {
"category_colorId_color_id_fk": { "category_colorId_color_id_fk": {
"name": "category_colorId_color_id_fk", "name": "category_colorId_color_id_fk",
@ -305,64 +276,60 @@
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {
"category_userId_code_unique": {
"name": "category_userId_code_unique",
"nullsNotDistinct": false,
"columns": [
"userId",
"code"
]
}
}
}, },
"color": { "public.color": {
"name": "color", "name": "color",
"schema": "",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"createdAt": { "createdAt": {
"name": "createdAt", "name": "createdAt",
"type": "integer", "type": "timestamp",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "default": "now()"
}, },
"name": { "name": {
"name": "name", "name": "name",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"hexcode": { "hexcode": {
"name": "hexcode", "name": "hexcode",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"inverse": { "inverse": {
"name": "inverse", "name": "inverse",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"userId": { "userId": {
"name": "userId", "name": "userId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}
},
"indexes": {
"color_userId_name_unique": {
"name": "color_userId_name_unique",
"columns": [
"userId",
"name"
],
"isUnique": true
} }
}, },
"indexes": {},
"foreignKeys": { "foreignKeys": {
"color_userId_user_id_fk": { "color_userId_user_id_fk": {
"name": "color_userId_user_id_fk", "name": "color_userId_user_id_fk",
@ -379,24 +346,32 @@
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {
"color_userId_name_unique": {
"name": "color_userId_name_unique",
"nullsNotDistinct": false,
"columns": [
"userId",
"name"
]
}
}
}, },
"config": { "public.config": {
"name": "config", "name": "config",
"schema": "",
"columns": { "columns": {
"key": { "key": {
"name": "key", "name": "key",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"value": { "value": {
"name": "value", "name": "value",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
} }
}, },
"indexes": {}, "indexes": {},
@ -404,54 +379,42 @@
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"day": { "public.day": {
"name": "day", "name": "day",
"schema": "",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"date": { "date": {
"name": "date", "name": "date",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"mood": { "mood": {
"name": "mood", "name": "mood",
"type": "integer", "type": "integer",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"comment": { "comment": {
"name": "comment", "name": "comment",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"userId": { "userId": {
"name": "userId", "name": "userId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}
},
"indexes": {
"day_date_unique": {
"name": "day_date_unique",
"columns": [
"date"
],
"isUnique": true
} }
}, },
"indexes": {},
"foreignKeys": { "foreignKeys": {
"day_userId_user_id_fk": { "day_userId_user_id_fk": {
"name": "day_userId_user_id_fk", "name": "day_userId_user_id_fk",
@ -468,71 +431,71 @@
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {
"day_date_unique": {
"name": "day_date_unique",
"nullsNotDistinct": false,
"columns": [
"date"
]
}
}
}, },
"hour": { "public.hour": {
"name": "hour", "name": "hour",
"schema": "",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"createdAt": { "createdAt": {
"name": "createdAt", "name": "createdAt",
"type": "integer", "type": "timestamp",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "default": "now()"
}, },
"userId": { "userId": {
"name": "userId", "name": "userId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"comment": { "comment": {
"name": "comment", "name": "comment",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"time": { "time": {
"name": "time", "name": "time",
"type": "integer", "type": "integer",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false },
"datetime": {
"name": "datetime",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}, },
"dayId": { "dayId": {
"name": "dayId", "name": "dayId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"categoryId": { "categoryId": {
"name": "categoryId", "name": "categoryId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}
},
"indexes": {
"hour_dayId_time_unique": {
"name": "hour_dayId_time_unique",
"columns": [
"dayId",
"time"
],
"isUnique": true
} }
}, },
"indexes": {},
"foreignKeys": { "foreignKeys": {
"hour_userId_user_id_fk": { "hour_userId_user_id_fk": {
"name": "hour_userId_user_id_fk", "name": "hour_userId_user_id_fk",
@ -575,59 +538,63 @@
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {
"hour_dayId_datetime_unique": {
"name": "hour_dayId_datetime_unique",
"nullsNotDistinct": false,
"columns": [
"dayId",
"datetime"
]
}
}
}, },
"measurement": { "public.measurement": {
"name": "measurement", "name": "measurement",
"schema": "",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"hourId": { "hourId": {
"name": "hourId", "name": "hourId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"dayId": { "dayId": {
"name": "dayId", "name": "dayId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"metricId": { "metricId": {
"name": "metricId", "name": "metricId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"createdAt": { "createdAt": {
"name": "createdAt", "name": "createdAt",
"type": "integer", "type": "timestamp",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "default": "now()"
}, },
"userId": { "userId": {
"name": "userId", "name": "userId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"value": { "value": {
"name": "value", "name": "value",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
} }
}, },
"indexes": {}, "indexes": {},
@ -688,83 +655,67 @@
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"metric": { "public.metric": {
"name": "metric", "name": "metric",
"schema": "",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"name": { "name": {
"name": "name", "name": "name",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"description": { "description": {
"name": "description", "name": "description",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"userId": { "userId": {
"name": "userId", "name": "userId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"type": { "type": {
"name": "type", "name": "type",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"unit": { "unit": {
"name": "unit", "name": "unit",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"goal": { "goal": {
"name": "goal", "name": "goal",
"type": "real", "type": "real",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"icon": { "icon": {
"name": "icon", "name": "icon",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"createdAt": { "createdAt": {
"name": "createdAt", "name": "createdAt",
"type": "integer", "type": "timestamp",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "default": "now()"
}
},
"indexes": {
"metric_userId_name_unique": {
"name": "metric_userId_name_unique",
"columns": [
"userId",
"name"
],
"isUnique": true
} }
}, },
"indexes": {},
"foreignKeys": { "foreignKeys": {
"metric_userId_user_id_fk": { "metric_userId_user_id_fk": {
"name": "metric_userId_user_id_fk", "name": "metric_userId_user_id_fk",
@ -781,31 +732,38 @@
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {
"metric_userId_name_unique": {
"name": "metric_userId_name_unique",
"nullsNotDistinct": false,
"columns": [
"userId",
"name"
]
}
}
}, },
"session": { "public.session": {
"name": "session", "name": "session",
"schema": "",
"columns": { "columns": {
"sessionToken": { "sessionToken": {
"name": "sessionToken", "name": "sessionToken",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"userId": { "userId": {
"name": "userId", "name": "userId",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"expires": { "expires": {
"name": "expires", "name": "expires",
"type": "integer", "type": "timestamp",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
} }
}, },
"indexes": {}, "indexes": {},
@ -827,57 +785,51 @@
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"user": { "public.user": {
"name": "user", "name": "user",
"schema": "",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"name": { "name": {
"name": "name", "name": "name",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"email": { "email": {
"name": "email", "name": "email",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"emailVerified": { "emailVerified": {
"name": "emailVerified", "name": "emailVerified",
"type": "integer", "type": "integer",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"image": { "image": {
"name": "image", "name": "image",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"password": { "password": {
"name": "password", "name": "password",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false
"autoincrement": false
}, },
"role": { "role": {
"name": "role", "name": "role",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false,
"autoincrement": false,
"default": "'user'" "default": "'user'"
}, },
"timezone": { "timezone": {
@ -885,69 +837,65 @@
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false,
"default": "'America/Los_Angeles'" "default": "'America/Los_Angeles'"
} }
}, },
"indexes": { "indexes": {},
"user_email_unique": {
"name": "user_email_unique",
"columns": [
"email"
],
"isUnique": true
}
},
"foreignKeys": {}, "foreignKeys": {},
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
}
}, },
"verificationToken": { "public.verificationToken": {
"name": "verificationToken", "name": "verificationToken",
"schema": "",
"columns": { "columns": {
"identifier": { "identifier": {
"name": "identifier", "name": "identifier",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"token": { "token": {
"name": "token", "name": "token",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
}, },
"expires": { "expires": {
"name": "expires", "name": "expires",
"type": "integer", "type": "timestamp",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true
"autoincrement": false
} }
}, },
"indexes": {}, "indexes": {},
"foreignKeys": {}, "foreignKeys": {},
"compositePrimaryKeys": { "compositePrimaryKeys": {
"verificationToken_identifier_token_pk": { "verificationToken_identifier_token_pk": {
"name": "verificationToken_identifier_token_pk",
"columns": [ "columns": [
"identifier", "identifier",
"token" "token"
], ]
"name": "verificationToken_identifier_token_pk"
} }
}, },
"uniqueConstraints": {} "uniqueConstraints": {}
} }
}, },
"enums": {}, "enums": {},
"schemas": {},
"sequences": {},
"_meta": { "_meta": {
"columns": {},
"schemas": {}, "schemas": {},
"tables": {}, "tables": {}
"columns": {}
},
"internal": {
"indexes": {}
} }
} }

View File

@ -0,0 +1,901 @@
{
"id": "d1116059-9901-4852-9826-5a4458564eb5",
"prevId": "8809abd6-6301-4dba-9f67-13071abd3086",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.account": {
"name": "account",
"schema": "",
"columns": {
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"providerAccountId": {
"name": "providerAccountId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"token_type": {
"name": "token_type",
"type": "text",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"session_state": {
"name": "session_state",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"account_userId_user_id_fk": {
"name": "account_userId_user_id_fk",
"tableFrom": "account",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {
"account_provider_providerAccountId_pk": {
"name": "account_provider_providerAccountId_pk",
"columns": [
"provider",
"providerAccountId"
]
}
},
"uniqueConstraints": {}
},
"public.apiKey": {
"name": "apiKey",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"keyId": {
"name": "keyId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"keyHash": {
"name": "keyHash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"apiKey_userId_user_id_fk": {
"name": "apiKey_userId_user_id_fk",
"tableFrom": "apiKey",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"apiKey_keyId_unique": {
"name": "apiKey_keyId_unique",
"columns": [
"keyId"
],
"nullsNotDistinct": false
},
"apiKey_name_userId_unique": {
"name": "apiKey_name_userId_unique",
"columns": [
"name",
"userId"
],
"nullsNotDistinct": false
}
}
},
"public.category": {
"name": "category",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"code": {
"name": "code",
"type": "real",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"colorId": {
"name": "colorId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"parentId": {
"name": "parentId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"category_colorId_color_id_fk": {
"name": "category_colorId_color_id_fk",
"tableFrom": "category",
"columnsFrom": [
"colorId"
],
"tableTo": "color",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "no action"
},
"category_parentId_category_id_fk": {
"name": "category_parentId_category_id_fk",
"tableFrom": "category",
"columnsFrom": [
"parentId"
],
"tableTo": "category",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "no action"
},
"category_userId_user_id_fk": {
"name": "category_userId_user_id_fk",
"tableFrom": "category",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"category_userId_code_unique": {
"name": "category_userId_code_unique",
"columns": [
"userId",
"code"
],
"nullsNotDistinct": false
}
}
},
"public.color": {
"name": "color",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"hexcode": {
"name": "hexcode",
"type": "text",
"primaryKey": false,
"notNull": true
},
"inverse": {
"name": "inverse",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"color_userId_user_id_fk": {
"name": "color_userId_user_id_fk",
"tableFrom": "color",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"color_userId_name_unique": {
"name": "color_userId_name_unique",
"columns": [
"userId",
"name"
],
"nullsNotDistinct": false
}
}
},
"public.config": {
"name": "config",
"schema": "",
"columns": {
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.day": {
"name": "day",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"date": {
"name": "date",
"type": "text",
"primaryKey": false,
"notNull": true
},
"mood": {
"name": "mood",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"comment": {
"name": "comment",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"day_userId_user_id_fk": {
"name": "day_userId_user_id_fk",
"tableFrom": "day",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"day_date_unique": {
"name": "day_date_unique",
"columns": [
"date"
],
"nullsNotDistinct": false
}
}
},
"public.hour": {
"name": "hour",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"comment": {
"name": "comment",
"type": "text",
"primaryKey": false,
"notNull": false
},
"time": {
"name": "time",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"datetime": {
"name": "datetime",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"dayId": {
"name": "dayId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"categoryId": {
"name": "categoryId",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"hour_userId_user_id_fk": {
"name": "hour_userId_user_id_fk",
"tableFrom": "hour",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
},
"hour_dayId_day_id_fk": {
"name": "hour_dayId_day_id_fk",
"tableFrom": "hour",
"columnsFrom": [
"dayId"
],
"tableTo": "day",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
},
"hour_categoryId_category_id_fk": {
"name": "hour_categoryId_category_id_fk",
"tableFrom": "hour",
"columnsFrom": [
"categoryId"
],
"tableTo": "category",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"hour_dayId_datetime_unique": {
"name": "hour_dayId_datetime_unique",
"columns": [
"dayId",
"datetime"
],
"nullsNotDistinct": false
}
}
},
"public.measurement": {
"name": "measurement",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"hourId": {
"name": "hourId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"dayId": {
"name": "dayId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"metricId": {
"name": "metricId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"measurement_hourId_hour_id_fk": {
"name": "measurement_hourId_hour_id_fk",
"tableFrom": "measurement",
"columnsFrom": [
"hourId"
],
"tableTo": "hour",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "no action"
},
"measurement_dayId_day_id_fk": {
"name": "measurement_dayId_day_id_fk",
"tableFrom": "measurement",
"columnsFrom": [
"dayId"
],
"tableTo": "day",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
},
"measurement_metricId_metric_id_fk": {
"name": "measurement_metricId_metric_id_fk",
"tableFrom": "measurement",
"columnsFrom": [
"metricId"
],
"tableTo": "metric",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
},
"measurement_userId_user_id_fk": {
"name": "measurement_userId_user_id_fk",
"tableFrom": "measurement",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.metric": {
"name": "metric",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"unit": {
"name": "unit",
"type": "text",
"primaryKey": false,
"notNull": false
},
"goal": {
"name": "goal",
"type": "real",
"primaryKey": false,
"notNull": false
},
"icon": {
"name": "icon",
"type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"metric_userId_user_id_fk": {
"name": "metric_userId_user_id_fk",
"tableFrom": "metric",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"metric_userId_name_unique": {
"name": "metric_userId_name_unique",
"columns": [
"userId",
"name"
],
"nullsNotDistinct": false
}
}
},
"public.session": {
"name": "session",
"schema": "",
"columns": {
"sessionToken": {
"name": "sessionToken",
"type": "text",
"primaryKey": true,
"notNull": true
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"session_userId_user_id_fk": {
"name": "session_userId_user_id_fk",
"tableFrom": "session",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"emailVerified": {
"name": "emailVerified",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'user'"
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'America/Los_Angeles'"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"columns": [
"email"
],
"nullsNotDistinct": false
}
}
},
"public.verificationToken": {
"name": "verificationToken",
"schema": "",
"columns": {
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"verificationToken_identifier_token_pk": {
"name": "verificationToken_identifier_token_pk",
"columns": [
"identifier",
"token"
]
}
},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"sequences": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,901 @@
{
"id": "708d11dd-ccaa-48dd-b152-f012b2412704",
"prevId": "d1116059-9901-4852-9826-5a4458564eb5",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.account": {
"name": "account",
"schema": "",
"columns": {
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"providerAccountId": {
"name": "providerAccountId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"token_type": {
"name": "token_type",
"type": "text",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"session_state": {
"name": "session_state",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"account_userId_user_id_fk": {
"name": "account_userId_user_id_fk",
"tableFrom": "account",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {
"account_provider_providerAccountId_pk": {
"name": "account_provider_providerAccountId_pk",
"columns": [
"provider",
"providerAccountId"
]
}
},
"uniqueConstraints": {}
},
"public.apiKey": {
"name": "apiKey",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"keyId": {
"name": "keyId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"keyHash": {
"name": "keyHash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"apiKey_userId_user_id_fk": {
"name": "apiKey_userId_user_id_fk",
"tableFrom": "apiKey",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"apiKey_keyId_unique": {
"name": "apiKey_keyId_unique",
"columns": [
"keyId"
],
"nullsNotDistinct": false
},
"apiKey_name_userId_unique": {
"name": "apiKey_name_userId_unique",
"columns": [
"name",
"userId"
],
"nullsNotDistinct": false
}
}
},
"public.category": {
"name": "category",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"code": {
"name": "code",
"type": "real",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"colorId": {
"name": "colorId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"parentId": {
"name": "parentId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"category_colorId_color_id_fk": {
"name": "category_colorId_color_id_fk",
"tableFrom": "category",
"columnsFrom": [
"colorId"
],
"tableTo": "color",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "no action"
},
"category_parentId_category_id_fk": {
"name": "category_parentId_category_id_fk",
"tableFrom": "category",
"columnsFrom": [
"parentId"
],
"tableTo": "category",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "no action"
},
"category_userId_user_id_fk": {
"name": "category_userId_user_id_fk",
"tableFrom": "category",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"category_userId_code_unique": {
"name": "category_userId_code_unique",
"columns": [
"userId",
"code"
],
"nullsNotDistinct": false
}
}
},
"public.color": {
"name": "color",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"hexcode": {
"name": "hexcode",
"type": "text",
"primaryKey": false,
"notNull": true
},
"inverse": {
"name": "inverse",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"color_userId_user_id_fk": {
"name": "color_userId_user_id_fk",
"tableFrom": "color",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"color_userId_name_unique": {
"name": "color_userId_name_unique",
"columns": [
"userId",
"name"
],
"nullsNotDistinct": false
}
}
},
"public.config": {
"name": "config",
"schema": "",
"columns": {
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.day": {
"name": "day",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"date": {
"name": "date",
"type": "text",
"primaryKey": false,
"notNull": true
},
"mood": {
"name": "mood",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"comment": {
"name": "comment",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"day_userId_user_id_fk": {
"name": "day_userId_user_id_fk",
"tableFrom": "day",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"day_date_unique": {
"name": "day_date_unique",
"columns": [
"date"
],
"nullsNotDistinct": false
}
}
},
"public.hour": {
"name": "hour",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"comment": {
"name": "comment",
"type": "text",
"primaryKey": false,
"notNull": false
},
"time": {
"name": "time",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"datetime": {
"name": "datetime",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"dayId": {
"name": "dayId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"categoryId": {
"name": "categoryId",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"hour_userId_user_id_fk": {
"name": "hour_userId_user_id_fk",
"tableFrom": "hour",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
},
"hour_dayId_day_id_fk": {
"name": "hour_dayId_day_id_fk",
"tableFrom": "hour",
"columnsFrom": [
"dayId"
],
"tableTo": "day",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
},
"hour_categoryId_category_id_fk": {
"name": "hour_categoryId_category_id_fk",
"tableFrom": "hour",
"columnsFrom": [
"categoryId"
],
"tableTo": "category",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"hour_dayId_datetime_unique": {
"name": "hour_dayId_datetime_unique",
"columns": [
"dayId",
"datetime"
],
"nullsNotDistinct": false
}
}
},
"public.measurement": {
"name": "measurement",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"hourId": {
"name": "hourId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"dayId": {
"name": "dayId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"metricId": {
"name": "metricId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"measurement_hourId_hour_id_fk": {
"name": "measurement_hourId_hour_id_fk",
"tableFrom": "measurement",
"columnsFrom": [
"hourId"
],
"tableTo": "hour",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "no action"
},
"measurement_dayId_day_id_fk": {
"name": "measurement_dayId_day_id_fk",
"tableFrom": "measurement",
"columnsFrom": [
"dayId"
],
"tableTo": "day",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
},
"measurement_metricId_metric_id_fk": {
"name": "measurement_metricId_metric_id_fk",
"tableFrom": "measurement",
"columnsFrom": [
"metricId"
],
"tableTo": "metric",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
},
"measurement_userId_user_id_fk": {
"name": "measurement_userId_user_id_fk",
"tableFrom": "measurement",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.metric": {
"name": "metric",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"unit": {
"name": "unit",
"type": "text",
"primaryKey": false,
"notNull": false
},
"goal": {
"name": "goal",
"type": "real",
"primaryKey": false,
"notNull": false
},
"icon": {
"name": "icon",
"type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"metric_userId_user_id_fk": {
"name": "metric_userId_user_id_fk",
"tableFrom": "metric",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"metric_userId_name_unique": {
"name": "metric_userId_name_unique",
"columns": [
"userId",
"name"
],
"nullsNotDistinct": false
}
}
},
"public.session": {
"name": "session",
"schema": "",
"columns": {
"sessionToken": {
"name": "sessionToken",
"type": "text",
"primaryKey": true,
"notNull": true
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"session_userId_user_id_fk": {
"name": "session_userId_user_id_fk",
"tableFrom": "session",
"columnsFrom": [
"userId"
],
"tableTo": "user",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"emailVerified": {
"name": "emailVerified",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'user'"
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'America/Los_Angeles'"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"columns": [
"email"
],
"nullsNotDistinct": false
}
}
},
"public.verificationToken": {
"name": "verificationToken",
"schema": "",
"columns": {
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"verificationToken_identifier_token_pk": {
"name": "verificationToken_identifier_token_pk",
"columns": [
"identifier",
"token"
]
}
},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"sequences": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,901 @@
{
"id": "2e969918-73b6-4418-bb7b-76acda909a8a",
"prevId": "708d11dd-ccaa-48dd-b152-f012b2412704",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.account": {
"name": "account",
"schema": "",
"columns": {
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"providerAccountId": {
"name": "providerAccountId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"token_type": {
"name": "token_type",
"type": "text",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"session_state": {
"name": "session_state",
"type": "text",
"primaryKey": false,
"notNull": 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": {
"name": "account_provider_providerAccountId_pk",
"columns": [
"provider",
"providerAccountId"
]
}
},
"uniqueConstraints": {}
},
"public.apiKey": {
"name": "apiKey",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"keyId": {
"name": "keyId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"keyHash": {
"name": "keyHash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"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": {
"apiKey_keyId_unique": {
"name": "apiKey_keyId_unique",
"nullsNotDistinct": false,
"columns": [
"keyId"
]
},
"apiKey_name_userId_unique": {
"name": "apiKey_name_userId_unique",
"nullsNotDistinct": false,
"columns": [
"name",
"userId"
]
}
}
},
"public.category": {
"name": "category",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"code": {
"name": "code",
"type": "real",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"colorId": {
"name": "colorId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"parentId": {
"name": "parentId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"category_colorId_color_id_fk": {
"name": "category_colorId_color_id_fk",
"tableFrom": "category",
"tableTo": "color",
"columnsFrom": [
"colorId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"category_parentId_category_id_fk": {
"name": "category_parentId_category_id_fk",
"tableFrom": "category",
"tableTo": "category",
"columnsFrom": [
"parentId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"category_userId_user_id_fk": {
"name": "category_userId_user_id_fk",
"tableFrom": "category",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"category_userId_code_unique": {
"name": "category_userId_code_unique",
"nullsNotDistinct": false,
"columns": [
"userId",
"code"
]
}
}
},
"public.color": {
"name": "color",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"hexcode": {
"name": "hexcode",
"type": "text",
"primaryKey": false,
"notNull": true
},
"inverse": {
"name": "inverse",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"color_userId_user_id_fk": {
"name": "color_userId_user_id_fk",
"tableFrom": "color",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"color_userId_name_unique": {
"name": "color_userId_name_unique",
"nullsNotDistinct": false,
"columns": [
"userId",
"name"
]
}
}
},
"public.config": {
"name": "config",
"schema": "",
"columns": {
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.day": {
"name": "day",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"date": {
"name": "date",
"type": "text",
"primaryKey": false,
"notNull": true
},
"mood": {
"name": "mood",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"comment": {
"name": "comment",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"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": {
"day_date_unique": {
"name": "day_date_unique",
"nullsNotDistinct": false,
"columns": [
"date"
]
}
}
},
"public.hour": {
"name": "hour",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"comment": {
"name": "comment",
"type": "text",
"primaryKey": false,
"notNull": false
},
"time": {
"name": "time",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"datetime": {
"name": "datetime",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"dayId": {
"name": "dayId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"categoryId": {
"name": "categoryId",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"hour_userId_user_id_fk": {
"name": "hour_userId_user_id_fk",
"tableFrom": "hour",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"hour_dayId_day_id_fk": {
"name": "hour_dayId_day_id_fk",
"tableFrom": "hour",
"tableTo": "day",
"columnsFrom": [
"dayId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"hour_categoryId_category_id_fk": {
"name": "hour_categoryId_category_id_fk",
"tableFrom": "hour",
"tableTo": "category",
"columnsFrom": [
"categoryId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"hour_dayId_datetime_unique": {
"name": "hour_dayId_datetime_unique",
"nullsNotDistinct": false,
"columns": [
"dayId",
"datetime"
]
}
}
},
"public.measurement": {
"name": "measurement",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"hourId": {
"name": "hourId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"dayId": {
"name": "dayId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"metricId": {
"name": "metricId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"measurement_hourId_hour_id_fk": {
"name": "measurement_hourId_hour_id_fk",
"tableFrom": "measurement",
"tableTo": "hour",
"columnsFrom": [
"hourId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"measurement_dayId_day_id_fk": {
"name": "measurement_dayId_day_id_fk",
"tableFrom": "measurement",
"tableTo": "day",
"columnsFrom": [
"dayId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"measurement_metricId_metric_id_fk": {
"name": "measurement_metricId_metric_id_fk",
"tableFrom": "measurement",
"tableTo": "metric",
"columnsFrom": [
"metricId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"measurement_userId_user_id_fk": {
"name": "measurement_userId_user_id_fk",
"tableFrom": "measurement",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.metric": {
"name": "metric",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"unit": {
"name": "unit",
"type": "text",
"primaryKey": false,
"notNull": false
},
"goal": {
"name": "goal",
"type": "real",
"primaryKey": false,
"notNull": false
},
"icon": {
"name": "icon",
"type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"metric_userId_user_id_fk": {
"name": "metric_userId_user_id_fk",
"tableFrom": "metric",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"metric_userId_name_unique": {
"name": "metric_userId_name_unique",
"nullsNotDistinct": false,
"columns": [
"userId",
"name"
]
}
}
},
"public.session": {
"name": "session",
"schema": "",
"columns": {
"sessionToken": {
"name": "sessionToken",
"type": "text",
"primaryKey": true,
"notNull": true
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"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": {}
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"emailVerified": {
"name": "emailVerified",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'user'"
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'America/Los_Angeles'"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
}
},
"public.verificationToken": {
"name": "verificationToken",
"schema": "",
"columns": {
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"verificationToken_identifier_token_pk": {
"name": "verificationToken_identifier_token_pk",
"columns": [
"identifier",
"token"
]
}
},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"sequences": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,895 @@
{
"id": "37a5291a-eed6-4576-a240-2825abdff575",
"prevId": "2e969918-73b6-4418-bb7b-76acda909a8a",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.account": {
"name": "account",
"schema": "",
"columns": {
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"providerAccountId": {
"name": "providerAccountId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"token_type": {
"name": "token_type",
"type": "text",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"session_state": {
"name": "session_state",
"type": "text",
"primaryKey": false,
"notNull": 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": {
"name": "account_provider_providerAccountId_pk",
"columns": [
"provider",
"providerAccountId"
]
}
},
"uniqueConstraints": {}
},
"public.apiKey": {
"name": "apiKey",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"keyId": {
"name": "keyId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"keyHash": {
"name": "keyHash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"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": {
"apiKey_keyId_unique": {
"name": "apiKey_keyId_unique",
"nullsNotDistinct": false,
"columns": [
"keyId"
]
},
"apiKey_name_userId_unique": {
"name": "apiKey_name_userId_unique",
"nullsNotDistinct": false,
"columns": [
"name",
"userId"
]
}
}
},
"public.category": {
"name": "category",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"code": {
"name": "code",
"type": "real",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"colorId": {
"name": "colorId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"parentId": {
"name": "parentId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"category_colorId_color_id_fk": {
"name": "category_colorId_color_id_fk",
"tableFrom": "category",
"tableTo": "color",
"columnsFrom": [
"colorId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"category_parentId_category_id_fk": {
"name": "category_parentId_category_id_fk",
"tableFrom": "category",
"tableTo": "category",
"columnsFrom": [
"parentId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"category_userId_user_id_fk": {
"name": "category_userId_user_id_fk",
"tableFrom": "category",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"category_userId_code_unique": {
"name": "category_userId_code_unique",
"nullsNotDistinct": false,
"columns": [
"userId",
"code"
]
}
}
},
"public.color": {
"name": "color",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"hexcode": {
"name": "hexcode",
"type": "text",
"primaryKey": false,
"notNull": true
},
"inverse": {
"name": "inverse",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"color_userId_user_id_fk": {
"name": "color_userId_user_id_fk",
"tableFrom": "color",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"color_userId_name_unique": {
"name": "color_userId_name_unique",
"nullsNotDistinct": false,
"columns": [
"userId",
"name"
]
}
}
},
"public.config": {
"name": "config",
"schema": "",
"columns": {
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.day": {
"name": "day",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"date": {
"name": "date",
"type": "text",
"primaryKey": false,
"notNull": true
},
"mood": {
"name": "mood",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"comment": {
"name": "comment",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"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": {
"day_date_unique": {
"name": "day_date_unique",
"nullsNotDistinct": false,
"columns": [
"date"
]
}
}
},
"public.hour": {
"name": "hour",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"comment": {
"name": "comment",
"type": "text",
"primaryKey": false,
"notNull": false
},
"datetime": {
"name": "datetime",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"dayId": {
"name": "dayId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"categoryId": {
"name": "categoryId",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"hour_userId_user_id_fk": {
"name": "hour_userId_user_id_fk",
"tableFrom": "hour",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"hour_dayId_day_id_fk": {
"name": "hour_dayId_day_id_fk",
"tableFrom": "hour",
"tableTo": "day",
"columnsFrom": [
"dayId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"hour_categoryId_category_id_fk": {
"name": "hour_categoryId_category_id_fk",
"tableFrom": "hour",
"tableTo": "category",
"columnsFrom": [
"categoryId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"hour_dayId_datetime_unique": {
"name": "hour_dayId_datetime_unique",
"nullsNotDistinct": false,
"columns": [
"dayId",
"datetime"
]
}
}
},
"public.measurement": {
"name": "measurement",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"hourId": {
"name": "hourId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"dayId": {
"name": "dayId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"metricId": {
"name": "metricId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"measurement_hourId_hour_id_fk": {
"name": "measurement_hourId_hour_id_fk",
"tableFrom": "measurement",
"tableTo": "hour",
"columnsFrom": [
"hourId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"measurement_dayId_day_id_fk": {
"name": "measurement_dayId_day_id_fk",
"tableFrom": "measurement",
"tableTo": "day",
"columnsFrom": [
"dayId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"measurement_metricId_metric_id_fk": {
"name": "measurement_metricId_metric_id_fk",
"tableFrom": "measurement",
"tableTo": "metric",
"columnsFrom": [
"metricId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"measurement_userId_user_id_fk": {
"name": "measurement_userId_user_id_fk",
"tableFrom": "measurement",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.metric": {
"name": "metric",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"unit": {
"name": "unit",
"type": "text",
"primaryKey": false,
"notNull": false
},
"goal": {
"name": "goal",
"type": "real",
"primaryKey": false,
"notNull": false
},
"icon": {
"name": "icon",
"type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"metric_userId_user_id_fk": {
"name": "metric_userId_user_id_fk",
"tableFrom": "metric",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"metric_userId_name_unique": {
"name": "metric_userId_name_unique",
"nullsNotDistinct": false,
"columns": [
"userId",
"name"
]
}
}
},
"public.session": {
"name": "session",
"schema": "",
"columns": {
"sessionToken": {
"name": "sessionToken",
"type": "text",
"primaryKey": true,
"notNull": true
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"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": {}
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"emailVerified": {
"name": "emailVerified",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'user'"
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'America/Los_Angeles'"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
}
},
"public.verificationToken": {
"name": "verificationToken",
"schema": "",
"columns": {
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"verificationToken_identifier_token_pk": {
"name": "verificationToken_identifier_token_pk",
"columns": [
"identifier",
"token"
]
}
},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"sequences": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -1,12 +1,40 @@
{ {
"version": "7", "version": "7",
"dialect": "sqlite", "dialect": "postgresql",
"entries": [ "entries": [
{ {
"idx": 0, "idx": 0,
"version": "6", "version": "7",
"when": 1733614471710, "when": 1738348952600,
"tag": "0000_moaning_thor", "tag": "0000_redundant_glorian",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1738348955500,
"tag": "0001_modify_hour_datetime",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1738368573808,
"tag": "0002_seed_ryan_user",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1738369747329,
"tag": "0003_silky_mongu",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1738371187519,
"tag": "0004_tan_justin_hammer",
"breakpoints": true "breakpoints": true
} }
] ]

View File

@ -9,24 +9,26 @@
"generate": "drizzle-kit generate", "generate": "drizzle-kit generate",
"reset": "tsx reset.ts", "reset": "tsx reset.ts",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"migrate": "tsx migrate.ts", "migrate": "drizzle-kit migrate",
"studio": "drizzle-kit studio" "studio": "drizzle-kit studio",
"drizzle-kit": "drizzle-kit"
}, },
"dependencies": { "dependencies": {
"@auth/core": "^0.37.3", "@auth/core": "^0.37.3",
"@paralleldrive/cuid2": "^2.2.2", "@paralleldrive/cuid2": "^2.2.2",
"better-sqlite3": "^11.3.0",
"dotenv": "^16.4.1", "dotenv": "^16.4.1",
"drizzle-orm": "^0.33.0", "drizzle-orm": "^0.33.0",
"expo-sqlite": "^14.0.6", "pg": "^8.11.3",
"tsx": "^4.7.1" "tsx": "^4.7.1"
}, },
"devDependencies": { "devDependencies": {
"better-sqlite3": "^11.3.0",
"@lifetracker/eslint-config": "workspace:*", "@lifetracker/eslint-config": "workspace:*",
"@lifetracker/typescript-config": "workspace:*", "@lifetracker/typescript-config": "workspace:*",
"@tsconfig/node21": "^21.0.1", "@tsconfig/node21": "^21.0.1",
"@types/better-sqlite3": "^7.6.9", "@types/pg": "^8.11.0",
"drizzle-kit": "^0.24.2", "drizzle-kit": "^0.24.2",
"pg-dump-restore": "^1.0.12",
"sqlite3": "^5.1.7" "sqlite3": "^5.1.7"
}, },
"eslintConfig": { "eslintConfig": {

View File

@ -1,23 +1,19 @@
import type { AdapterAccount } from "@auth/core/adapters"; import type { AdapterAccount } from "@auth/core/adapters";
import { createId } from "@paralleldrive/cuid2"; import { createId } from "@paralleldrive/cuid2";
import { relations, SQL, sql } from "drizzle-orm"; import { relations, SQL, sql } from "drizzle-orm";
import { date } from "drizzle-orm/mysql-core"; import { time, timestamp } from "drizzle-orm/pg-core";
import { import {
AnySQLiteColumn, pgTable,
index,
integer, integer,
real, real,
primaryKey, primaryKey,
sqliteTable,
text, text,
unique, unique,
} from "drizzle-orm/sqlite-core"; } from "drizzle-orm/pg-core";
function createdAtField() { function createdAtField() {
return integer("createdAt", { mode: "timestamp" }) return timestamp("createdAt", { withTimezone: true }).notNull().defaultNow();
.notNull()
.$defaultFn(() => new Date());
} }
export function calcInverseColor(hexcode: string): string { export function calcInverseColor(hexcode: string): string {
@ -35,12 +31,12 @@ export function calcInverseColor(hexcode: string): string {
export const config = sqliteTable("config", { export const config = pgTable("config", {
key: text("key").notNull().primaryKey(), key: text("key").notNull().primaryKey(),
value: text("value").notNull(), value: text("value").notNull(),
}); });
export const apiKeys = sqliteTable( export const apiKeys = pgTable(
"apiKey", "apiKey",
{ {
id: text("id") id: text("id")
@ -61,21 +57,21 @@ export const apiKeys = sqliteTable(
); );
export const users = sqliteTable("user", { export const users = pgTable("user", {
id: text("id") id: text("id")
.notNull() .notNull()
.primaryKey() .primaryKey()
.$defaultFn(() => createId()), .$defaultFn(() => createId()),
name: text("name").notNull(), name: text("name").notNull(),
email: text("email").notNull().unique(), email: text("email").notNull().unique(),
emailVerified: integer("emailVerified", { mode: "timestamp_ms" }), emailVerified: integer("emailVerified"),
image: text("image"), image: text("image"),
password: text("password"), password: text("password"),
role: text("role", { enum: ["admin", "user"] }).default("user"), role: text("role", { enum: ["admin", "user"] }).default("user"),
timezone: text("timezone").notNull().default("America/Los_Angeles"), timezone: text("timezone").notNull().default("America/Los_Angeles"),
}); });
export const accounts = sqliteTable( export const accounts = pgTable(
"account", "account",
{ {
userId: text("userId") userId: text("userId")
@ -99,7 +95,7 @@ export const accounts = sqliteTable(
}), }),
); );
export const sessions = sqliteTable("session", { export const sessions = pgTable("session", {
sessionToken: text("sessionToken") sessionToken: text("sessionToken")
.notNull() .notNull()
.primaryKey() .primaryKey()
@ -107,22 +103,22 @@ export const sessions = sqliteTable("session", {
userId: text("userId") userId: text("userId")
.notNull() .notNull()
.references(() => users.id, { onDelete: "cascade" }), .references(() => users.id, { onDelete: "cascade" }),
expires: integer("expires", { mode: "timestamp_ms" }).notNull(), expires: timestamp("expires").notNull(),
}); });
export const verificationTokens = sqliteTable( export const verificationTokens = pgTable(
"verificationToken", "verificationToken",
{ {
identifier: text("identifier").notNull(), identifier: text("identifier").notNull(),
token: text("token").notNull(), token: text("token").notNull(),
expires: integer("expires", { mode: "timestamp_ms" }).notNull(), expires: timestamp("expires").notNull(),
}, },
(vt) => ({ (vt) => ({
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }),
}), }),
); );
export const metrics = sqliteTable("metric", { export const metrics = pgTable("metric", {
id: text("id") id: text("id")
.notNull() .notNull()
.primaryKey() .primaryKey()
@ -143,7 +139,7 @@ export const metrics = sqliteTable("metric", {
}), }),
); );
export const measurements = sqliteTable("measurement", { export const measurements = pgTable("measurement", {
id: text("id") id: text("id")
.notNull() .notNull()
.primaryKey() .primaryKey()
@ -162,7 +158,7 @@ export const measurements = sqliteTable("measurement", {
}), }),
); );
export const days = sqliteTable("day", { export const days = pgTable("day", {
id: text("id") id: text("id")
.notNull() .notNull()
.primaryKey() .primaryKey()
@ -175,7 +171,7 @@ export const days = sqliteTable("day", {
.references(() => users.id, { onDelete: "cascade" }), .references(() => users.id, { onDelete: "cascade" }),
}); });
export const hours = sqliteTable( export const hours = pgTable(
"hour", "hour",
{ {
id: text("id") id: text("id")
@ -187,17 +183,17 @@ export const hours = sqliteTable(
.notNull() .notNull()
.references(() => users.id, { onDelete: "cascade" }), .references(() => users.id, { onDelete: "cascade" }),
comment: text("comment"), comment: text("comment"),
time: integer("time").notNull(), datetime: timestamp("datetime").notNull(),
dayId: text("dayId").notNull().references(() => days.id, { onDelete: "cascade" }), dayId: text("dayId").notNull().references(() => days.id, { onDelete: "cascade" }),
categoryId: text("categoryId").references(() => categories.id), categoryId: text("categoryId").references(() => categories.id),
}, },
(e) => ({ (e) => ({
uniq: unique().on(e.dayId, e.time) uniq: unique().on(e.dayId, e.datetime)
}), }),
) )
export const colors = sqliteTable( export const colors = pgTable(
"color", "color",
{ {
id: text("id") id: text("id")
@ -220,7 +216,7 @@ export const colors = sqliteTable(
// and fuck if the in built documentation makes any sense // and fuck if the in built documentation makes any sense
}), }),
); );
export const categories = sqliteTable( export const categories = pgTable(
"category", "category",
{ {
id: text("id") id: text("id")
@ -254,7 +250,11 @@ export const apiKeyRelations = relations(apiKeys, ({ one }) => ({
references: [users.id], references: [users.id],
}), }),
})); }));
export const userRelations = relations(users, ({ many }) => ({ export const userRelations = relations(users, ({ many, one }) => ({
apiKeys: one(apiKeys, {
fields: [users.id],
references: [apiKeys.userId],
}),
categories: many(categories), categories: many(categories),
colors: many(colors), colors: many(colors),
days: many(days), days: many(days),
@ -348,9 +348,9 @@ export const measurementsRelations = relations(
fields: [measurements.dayId], fields: [measurements.dayId],
references: [days.id], references: [days.id],
}), }),
hour: measurements.hourId ? one(hours, { hour: one(hours, {
fields: [measurements.hourId], fields: [measurements.hourId],
references: [hours.id], references: [hours.id],
}) : undefined, }),
}), }),
); );

View File

@ -1,7 +1,7 @@
import { z } from "zod"; import { z } from "zod";
import dotenv from "dotenv"; import dotenv from "dotenv";
dotenv.config({ dotenv.config({
path: ".env.local", path: "../../.env.local",
}); });
const stringBool = (defaultValue: string) => const stringBool = (defaultValue: string) =>
z z
@ -55,6 +55,8 @@ const allEnv = z.object({
DEMO_MODE_EMAIL: z.string().optional(), DEMO_MODE_EMAIL: z.string().optional(),
DEMO_MODE_PASSWORD: z.string().optional(), DEMO_MODE_PASSWORD: z.string().optional(),
DATA_DIR: z.string().default(""), DATA_DIR: z.string().default(""),
DATABASE_URL: z.string().default("postgres://lifetracker:pleasework@postgresql.home:5432/lifetracker"),
TEST_DATABASE_URL: z.string().default("postgres://lifetracker:pleasework@postgresql.home:5432/lifetracker_test"),
MAX_ASSET_SIZE_MB: z.coerce.number().default(4), MAX_ASSET_SIZE_MB: z.coerce.number().default(4),
INFERENCE_LANG: z.string().default("english"), INFERENCE_LANG: z.string().default("english"),
// Build only flag // Build only flag
@ -127,6 +129,8 @@ const serverConfigSchema = allEnv.transform((val) => {
} }
: undefined, : undefined,
dataDir: val.DATA_DIR, dataDir: val.DATA_DIR,
databaseUrl: val.DATABASE_URL,
testDatabaseUrl: val.TEST_DATABASE_URL,
maxAssetSizeMb: val.MAX_ASSET_SIZE_MB, maxAssetSizeMb: val.MAX_ASSET_SIZE_MB,
serverVersion: val.SERVER_VERSION, serverVersion: val.SERVER_VERSION,
disableNewReleaseCheck: val.DISABLE_NEW_RELEASE_CHECK, disableNewReleaseCheck: val.DISABLE_NEW_RELEASE_CHECK,

View File

@ -6,8 +6,7 @@ export const zHourSchema = z.object({
id: z.string().optional(), id: z.string().optional(),
dayId: z.string(), dayId: z.string(),
date: z.string().optional(), date: z.string().optional(),
time: z.number(), datetime: z.date(),
datetime: z.string().optional(),
categoryCode: z.coerce.number().nullish(), categoryCode: z.coerce.number().nullish(),
categoryId: z.string().nullish(), categoryId: z.string().nullish(),
categoryName: z.string().nullish(), categoryName: z.string().nullish(),

View File

@ -1,4 +1,3 @@
import { apiKeys } from "@lifetracker/db/schema";
import { router } from "../index"; import { router } from "../index";
import { usersAppRouter } from "./users"; import { usersAppRouter } from "./users";
import { apiKeysAppRouter } from "./apiKeys"; import { apiKeysAppRouter } from "./apiKeys";

View File

@ -2,7 +2,7 @@ import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
import { and, desc, eq, inArray, notExists } from "drizzle-orm"; import { and, desc, eq, inArray, notExists } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { SqliteError } from "@lifetracker/db"; import { DatabaseError } from "@lifetracker/db";
import { categories, colors } from "@lifetracker/db/schema"; import { categories, colors } from "@lifetracker/db/schema";
import { import {
ZCategories, ZCategories,
@ -54,8 +54,8 @@ async function createCategory(
}; };
} catch (e) { } catch (e) {
if (e instanceof SqliteError) { if (e instanceof DatabaseError) {
if (e.code == "SQLITE_CONSTRAINT_UNIQUE") { if (e.code == "23505") {
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: "There's already a category with this code", message: "There's already a category with this code",

View File

@ -2,7 +2,7 @@ import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
import { and, desc, eq, inArray, notExists } from "drizzle-orm"; import { and, desc, eq, inArray, notExists } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { SqliteError } from "@lifetracker/db"; import { DatabaseError, ErrorCodes } from "@lifetracker/db";
import { colors, calcInverseColor } from "@lifetracker/db/schema"; import { colors, calcInverseColor } from "@lifetracker/db/schema";
import { import {
zColorSchema, ZColor zColorSchema, ZColor
@ -37,8 +37,8 @@ async function createColor(
}); });
return result[0]; return result[0];
} catch (e) { } catch (e) {
if (e instanceof SqliteError) { if (e instanceof DatabaseError) {
if (e.code == "SQLITE_CONSTRAINT_UNIQUE") { if (e.code == ErrorCodes.UNIQUE_VIOLATION) {
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: "This color already exists", message: "This color already exists",

View File

@ -2,7 +2,7 @@ import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
import { and, desc, eq, inArray, notExists } from "drizzle-orm"; import { and, desc, eq, inArray, notExists } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { db, SqliteError } from "@lifetracker/db"; import { db, DatabaseError, ErrorCodes } from "@lifetracker/db";
import { categories, days, hours, users } from "@lifetracker/db/schema"; import { categories, days, hours, users } from "@lifetracker/db/schema";
import { import {
zDaySchema, ZDay, zDaySchema, ZDay,
@ -11,7 +11,7 @@ import {
import type { Context } from "../index"; import type { Context } from "../index";
import { authedProcedure, router } from "../index"; import { authedProcedure, router } from "../index";
import { dateFromInput, hoursListInUTC } from "@lifetracker/shared/utils/days"; import { dateFromInput, hoursListInUTC } from "@lifetracker/shared/utils/days";
import { closestIndexTo, format } from "date-fns"; import { addDays, closestIndexTo, format, parseISO } from "date-fns";
import { TZDate } from "@date-fns/tz"; import { TZDate } from "@date-fns/tz";
import { hoursAppRouter, hourColors, hourJoinsQuery, getHours, createHour } from "./hours"; import { hoursAppRouter, hourColors, hourJoinsQuery, getHours, createHour } from "./hours";
import spacetime from "spacetime"; import spacetime from "spacetime";
@ -36,8 +36,8 @@ export async function createDay(date: string, ctx: Context) {
return dayRes[0]; return dayRes[0];
} catch (e) { } catch (e) {
if (e instanceof SqliteError) { if (e instanceof DatabaseError) {
if (e.code == "SQLITE_CONSTRAINT_UNIQUE") { if (e.code == ErrorCodes.UNIQUE_VIOLATION) {
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: "This day already exists", message: "This day already exists",
@ -75,6 +75,27 @@ export async function getDay(ctx: Context, input: { dateQuery: string, timezone:
} }
} }
export function listOfDates(dateRange: [Date, Date]) {
const [start, end] = dateRange.map((date) => dateFromInput({
dateQuery: spacetime(date, "UTC").goto("UTC").format("iso-short"),
timezone: "Etc/UTC"
}));
const dates = [];
let currentDate = parseISO(start);
while (currentDate <= parseISO(end)) {
dates.push(format(currentDate, "yyyy-MM-dd"));
currentDate = addDays(currentDate, 1);
}
return dates.length === 0 ? [format(parseISO(start), "yyyy-MM-dd")] : dates;
}
export function listOfDayIds(dateRange: [Date, Date]) {
return listOfDates(dateRange).map((date) =>
db.select({ id: days.id })
.from(days)
.where(eq(days.date, date))
);
}
export const daysAppRouter = router({ export const daysAppRouter = router({
get: authedProcedure get: authedProcedure
.input(z.object({ .input(z.object({

View File

@ -1,27 +1,23 @@
import { experimental_trpcMiddleware, TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { and, desc, eq, inArray, notExists } from "drizzle-orm"; import { and, desc, eq, inArray, notExists } from "drizzle-orm";
import { date, z } from "zod"; import { z } from "zod";
import { SqliteError } from "@lifetracker/db"; import { DatabaseError } from "@lifetracker/db";
import { categories, days, hours, colors, measurements, metrics } from "@lifetracker/db/schema"; import { categories, days, hours, colors, measurements, metrics } from "@lifetracker/db/schema";
import { import {
zDaySchema, ZDay, ZHour, zHourSchema ZDay, ZHour, zHourSchema
} from "@lifetracker/shared/types/days"; } from "@lifetracker/shared/types/days";
import type { Context } from "../index"; import type { Context } from "../index";
import { authedProcedure, router } from "../index"; import { authedProcedure, router } from "../index";
import { addDays, format, parseISO } from "date-fns";
import { TZDate } from "@date-fns/tz";
import { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
import { zCategorySchema } from "@lifetracker/shared/types/categories";
import spacetime from "spacetime"; import spacetime from "spacetime";
import { dateFromInput, hoursListInUTC } from "@lifetracker/shared/utils/days"; import { hoursListInUTC } from "@lifetracker/shared/utils/days";
import { createDay, getDay } from "./days"; import { createDay, listOfDates } from "./days";
export async function createHour(day: ZDay | { id: string }, time: number, ctx: Context,) { export async function createHour(day: { id: string }, datetime: Date, ctx: Context,) {
const newHour = (await ctx.db.insert(hours).values({ const newHour = (await ctx.db.insert(hours).values({
dayId: day.id!, dayId: day.id,
time: time, datetime: datetime,
userId: ctx.user!.id, userId: ctx.user!.id,
}).returning()); }).returning());
return newHour[0]; return newHour[0];
@ -52,7 +48,7 @@ export async function hourColors(hour: ZHour, ctx: Context) {
export async function hourJoinsQuery( export async function hourJoinsQuery(
ctx: Context, ctx: Context,
time: number, datetime: Date,
dayId?: string, dayId?: string,
day?: ZDay, day?: ZDay,
) { ) {
@ -67,7 +63,7 @@ export async function hourJoinsQuery(
const res = await ctx.db.select({ const res = await ctx.db.select({
id: hours.id, id: hours.id,
dayId: hours.dayId, dayId: hours.dayId,
time: hours.time, datetime: hours.datetime,
categoryId: hours.categoryId, categoryId: hours.categoryId,
categoryCode: categories.code, categoryCode: categories.code,
categoryName: categories.name, categoryName: categories.name,
@ -78,11 +74,11 @@ export async function hourJoinsQuery(
.leftJoin(categories, eq(categories.id, hours.categoryId)) .leftJoin(categories, eq(categories.id, hours.categoryId))
.leftJoin(days, eq(days.id, hours.dayId)) .leftJoin(days, eq(days.id, hours.dayId))
.where(and( .where(and(
eq(hours.time, time), eq(hours.datetime, datetime),
eq(hours.dayId, id) eq(hours.dayId, id)
)); ));
const hourMatch = res[0] ?? createHour({ id: id }, time, ctx); const hourMatch = res[0] ?? createHour({ id }, datetime, ctx);
const hourMeasurements = await ctx.db.select({ const hourMeasurements = await ctx.db.select({
@ -131,33 +127,16 @@ export async function getHours(ctx: Context, input: { dateQuery: string | [Date,
// Finally, use the two unique day IDs and the 24 hours to get the actual Hour objects for each day // Finally, use the two unique day IDs and the 24 hours to get the actual Hour objects for each day
const dayHours = await Promise.all(uniqueHours.map(async function (map: { date: string, time: number }, i) { const dayHours = await Promise.all(uniqueHours.map(async function (map: { date: string, time: number }, i) {
const dayId = uniqueDayIds.find((dayIds: { id: string, date: string }) => map.date == dayIds.date)!.id; const dayId = uniqueDayIds.find((dayIds: { id: string, date: string }) => map.date == dayIds.date)!.id;
const datetime = spacetime(map.date, "Etc/UTC").hour(map.time).toNativeDate();
return hourJoinsQuery(ctx, map.time, dayId); return hourJoinsQuery(ctx, datetime, dayId);
})); }));
return dayHours; return dayHours;
} }
function listOfDates(dateRange: [Date, Date]) {
const [start, end] = dateRange.map((date) => dateFromInput({
dateQuery: spacetime(date, "UTC").goto("UTC").format("iso-short"),
timezone: "Etc/UTC"
}));
const dates = [];
let currentDate = parseISO(start);
while (currentDate <= parseISO(end)) {
dates.push(format(currentDate, "yyyy-MM-dd"));
currentDate = addDays(currentDate, 1);
}
return dates.length === 0 ? [format(parseISO(start), "yyyy-MM-dd")] : dates;
}
export const hoursAppRouter = router({ export const hoursAppRouter = router({
get: authedProcedure get: authedProcedure
.input(z.object({ .input(z.object({
dateQuery: z.string(), dateQuery: z.string(),
time: z.number()
})) }))
.output(zHourSchema) .output(zHourSchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
@ -165,8 +144,7 @@ export const hoursAppRouter = router({
.leftJoin(days, eq(days.id, hours.dayId)) .leftJoin(days, eq(days.id, hours.dayId))
.where( .where(
and( and(
eq(hours.time, input.time), eq(hours.datetime, spacetime(input.dateQuery, "Etc/UTC").toNativeDate()),
eq(days.date, input.dateQuery),
eq(hours.userId, ctx.user!.id) eq(hours.userId, ctx.user!.id)
) )
); );
@ -255,10 +233,20 @@ export const hoursAppRouter = router({
} }
))) )))
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
// console.log(input.dateRange, input.timezone);
const hoursList = await getHours(ctx, { const hoursList = await getHours(ctx, {
dateQuery: input.dateRange, dateQuery: input.dateRange,
timezone: input.timezone timezone: input.timezone
}); });
// console.log(hoursList.map(h => {
// return {
// datetime: spacetime(h.datetime).goto("America/Los_Angeles").format("{iso-short} {hour-24}:{minute}"),
// id: h.id,
// date: h.date,
// };
// }));
// Count total hours in the filtered range // Count total hours in the filtered range
const totalHours = hoursList.length; const totalHours = hoursList.length;

View File

@ -1,14 +1,17 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { and, desc, eq, inArray, notExists } from "drizzle-orm"; import { and, desc, eq, inArray, notExists, sql } from "drizzle-orm";
import { z, ZodNull } from "zod"; import { z, ZodNull } from "zod";
import { SqliteError } from "@lifetracker/db"; import { DatabaseError } from "@lifetracker/db";
import { colors, metrics, measurements, hours } from "@lifetracker/db/schema"; import { colors, metrics, measurements, hours, days } from "@lifetracker/db/schema";
import { zMetricSchema, zMeasurementSchema } from "@lifetracker/shared/types/metrics"; import { zMetricSchema, zMeasurementSchema } from "@lifetracker/shared/types/metrics";
import type { Context } from "../index"; import type { Context } from "../index";
import { authedProcedure, router } from "../index"; import { authedProcedure, router } from "../index";
import { zColorSchema } from "@lifetracker/shared/types/colors"; import { zColorSchema } from "@lifetracker/shared/types/colors";
import { titleCase } from "title-case"; import { titleCase } from "title-case";
import { listOfDates, listOfDayIds } from "./days";
import { getHours } from "./hours";
import { datetime } from "drizzle-orm/mysql-core";
const getMetricFromInput = async (ctx: Context, input: const getMetricFromInput = async (ctx: Context, input:
{ metricId?: string | null | undefined; metricName?: string | null | undefined; }) => { { metricId?: string | null | undefined; metricName?: string | null | undefined; }) => {
@ -181,4 +184,44 @@ export const measurementsAppRouter = router({
return 0; return 0;
} }
}), }),
getTimeseries: authedProcedure
.input(z.object({
dateRange: z.tuple([z.date(), z.date()]),
timezone: z.string(),
metricName: z.string()
}))
.output(z.array(z.object({
id: z.string(),
value: z.string(),
unit: z.string(),
datetime: z.date(),
})
))
.query(async ({ input, ctx }) => {
const hoursList = await getHours(ctx, {
dateQuery: input.dateRange,
timezone: input.timezone,
});
const hourIds = hoursList.map((hour) => hour.id);
const metric = await ctx.db.select().from(metrics).where(eq(metrics.name, titleCase(input.metricName)));
if (!metric[0]) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Metric not found",
});
}
const measurementsList = await ctx.db.select({
id: measurements.id,
value: measurements.value,
unit: metrics.unit,
datetime: hours.datetime,
}).from(measurements)
.leftJoin(metrics, eq(measurements.metricId, metrics.id))
.leftJoin(hours, eq(measurements.hourId, hours.id))
.where(and(
eq(measurements.metricId, metric[0].id),
inArray(measurements.hourId, hourIds),
));
return measurementsList;
}),
}); });

View File

@ -2,7 +2,7 @@ import { TRPCError } from "@trpc/server";
import { and, desc, eq, inArray, notExists } from "drizzle-orm"; import { and, desc, eq, inArray, notExists } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { SqliteError } from "@lifetracker/db"; import { DatabaseError, ErrorCodes } from "@lifetracker/db";
import { colors, metrics } from "@lifetracker/db/schema"; import { colors, metrics } from "@lifetracker/db/schema";
import { zMetricSchema, zMeasurementSchema } from "@lifetracker/shared/types/metrics"; import { zMetricSchema, zMeasurementSchema } from "@lifetracker/shared/types/metrics";
import type { Context } from "../index"; import type { Context } from "../index";
@ -40,8 +40,8 @@ export const metricsAppRouter = router({
.returning(); .returning();
return result[0]; return result[0];
} catch (e) { } catch (e) {
if (e instanceof SqliteError) { if (e instanceof DatabaseError) {
if (e.code == "SQLITE_CONSTRAINT_UNIQUE") { if (e.code == ErrorCodes.UNIQUE_VIOLATION) {
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: "This metric already exists", message: "This metric already exists",

View File

@ -3,7 +3,7 @@ import { and, count, eq } from "drizzle-orm";
import invariant from "tiny-invariant"; import invariant from "tiny-invariant";
import { z } from "zod"; import { z } from "zod";
import { SqliteError } from "@lifetracker/db"; import { DatabaseError, ErrorCodes } from "@lifetracker/db";
import { users } from "@lifetracker/db/schema"; import { users } from "@lifetracker/db/schema";
import serverConfig from "@lifetracker/shared/config"; import serverConfig from "@lifetracker/shared/config";
import { zSignUpSchema } from "@lifetracker/shared/types/users"; import { zSignUpSchema } from "@lifetracker/shared/types/users";
@ -49,8 +49,8 @@ export async function createUser(
}); });
return result[0]; return result[0];
} catch (e) { } catch (e) {
if (e instanceof SqliteError) { if (e instanceof DatabaseError) {
if (e.code == "SQLITE_CONSTRAINT_UNIQUE") { if (e.code == ErrorCodes.UNIQUE_VIOLATION) {
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: "Email is already taken", message: "Email is already taken",

742
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff