feat: create tables for habit and intake trackers
This commit is contained in:
parent
2a1caa197b
commit
9097a4183b
86
api/drizzle/0001_stormy_gertrude_yorkes.sql
Normal file
86
api/drizzle/0001_stormy_gertrude_yorkes.sql
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
CREATE SCHEMA "habit_tracker";
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE SCHEMA "intake_tracker";
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "habit_tracker"."habit" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"user_id" uuid NOT NULL,
|
||||||
|
"name" varchar(255) NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"frequency_type" varchar(20) NOT NULL,
|
||||||
|
"target_count" integer DEFAULT 1 NOT NULL,
|
||||||
|
"interval_days" integer,
|
||||||
|
"active" boolean DEFAULT true NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"deleted_at" timestamp,
|
||||||
|
CONSTRAINT "freqency_type_check" CHECK ("habit_tracker"."habit"."frequency_type" IN ('daily', 'interval', 'multi_daily')),
|
||||||
|
CONSTRAINT "target_count_check" CHECK ("habit_tracker"."habit"."target_count" > 0),
|
||||||
|
CONSTRAINT "interval_days_check" CHECK (("habit_tracker"."habit"."frequency_type" = 'interval' AND "habit_tracker"."habit"."interval_days" IS NOT NULL AND "habit_tracker"."habit"."interval_days" > 0) OR ("habit_tracker"."habit"."frequency_type" != 'interval' AND "habit_tracker"."habit"."interval_days" IS NULL))
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "habit_tracker"."habit_completion" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"habit_id" uuid NOT NULL,
|
||||||
|
"notes" text,
|
||||||
|
"completed_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"deleted_at" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "intake_tracker"."daily_summary" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"intake_metric_id" uuid NOT NULL,
|
||||||
|
"date" date NOT NULL,
|
||||||
|
"total_value" numeric(10, 2) NOT NULL,
|
||||||
|
"entry_count" integer NOT NULL,
|
||||||
|
"first_entry_at" timestamp,
|
||||||
|
"last_entry_at" timestamp,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"deleted_at" timestamp,
|
||||||
|
CONSTRAINT "daily_summary_intake_metric_id_date_unique" UNIQUE("intake_metric_id","date"),
|
||||||
|
CONSTRAINT "positive_total_check" CHECK ("intake_tracker"."daily_summary"."total_value" > 0),
|
||||||
|
CONSTRAINT "positive_count_check" CHECK ("intake_tracker"."daily_summary"."entry_count" > 0)
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "intake_tracker"."intake_metric" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"user_id" uuid NOT NULL,
|
||||||
|
"metric_type" varchar(50) NOT NULL,
|
||||||
|
"unit" varchar(20) NOT NULL,
|
||||||
|
"display_name" varchar(100) NOT NULL,
|
||||||
|
"target_value" numeric(10, 2),
|
||||||
|
"min_value" numeric(10, 2),
|
||||||
|
"max_value" numeric(10, 2),
|
||||||
|
"is_cumulative" boolean DEFAULT true NOT NULL,
|
||||||
|
"active" boolean DEFAULT true NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"deleted_at" timestamp,
|
||||||
|
CONSTRAINT "intake_metric_user_id_metric_type_unique" UNIQUE("user_id","metric_type"),
|
||||||
|
CONSTRAINT "positive_target_check" CHECK ("intake_tracker"."intake_metric"."target_value" IS NULL OR "intake_tracker"."intake_metric"."target_value" > 0),
|
||||||
|
CONSTRAINT "positive_min_check" CHECK ("intake_tracker"."intake_metric"."min_value" IS NULL OR "intake_tracker"."intake_metric"."min_value" >= 0),
|
||||||
|
CONSTRAINT "positive_max_check" CHECK ("intake_tracker"."intake_metric"."max_value" IS NULL OR "intake_tracker"."intake_metric"."max_value" >= 0),
|
||||||
|
CONSTRAINT "min_max_check" CHECK ("intake_tracker"."intake_metric"."min_value" IS NULL OR "intake_tracker"."intake_metric"."max_value" IS NULL OR "intake_tracker"."intake_metric"."min_value" <= "intake_tracker"."intake_metric"."max_value")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "intake_tracker"."intake_record" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"intake_metric_id" uuid NOT NULL,
|
||||||
|
"value" numeric(10, 2),
|
||||||
|
"recorded_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"notes" text,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"deleted_at" timestamp,
|
||||||
|
CONSTRAINT "positive_value_check" CHECK ("intake_tracker"."intake_record"."value" > 0),
|
||||||
|
CONSTRAINT "recorded_at_not_future_check" CHECK ("intake_tracker"."intake_record"."recorded_at" <= NOW())
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "habit_tracker"."habit" ADD CONSTRAINT "habit_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "shared"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "habit_tracker"."habit_completion" ADD CONSTRAINT "habit_completion_habit_id_habit_id_fk" FOREIGN KEY ("habit_id") REFERENCES "habit_tracker"."habit"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "intake_tracker"."daily_summary" ADD CONSTRAINT "daily_summary_intake_metric_id_intake_metric_id_fk" FOREIGN KEY ("intake_metric_id") REFERENCES "intake_tracker"."intake_metric"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "intake_tracker"."intake_metric" ADD CONSTRAINT "intake_metric_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "shared"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "intake_tracker"."intake_record" ADD CONSTRAINT "intake_record_intake_metric_id_intake_metric_id_fk" FOREIGN KEY ("intake_metric_id") REFERENCES "intake_tracker"."intake_metric"("id") ON DELETE cascade ON UPDATE no action;
|
1028
api/drizzle/meta/0001_snapshot.json
Normal file
1028
api/drizzle/meta/0001_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,13 @@
|
|||||||
"when": 1752443645228,
|
"when": 1752443645228,
|
||||||
"tag": "0000_hard_violations",
|
"tag": "0000_hard_violations",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1752445599098,
|
||||||
|
"tag": "0001_stormy_gertrude_yorkes",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
53
api/src/db/schema/habit.ts
Normal file
53
api/src/db/schema/habit.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
boolean,
|
||||||
|
check,
|
||||||
|
integer,
|
||||||
|
pgSchema,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
uuid,
|
||||||
|
varchar,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { user } from "./auth";
|
||||||
|
import { idPrimaryKey, timestampSchema } from "./helpers";
|
||||||
|
|
||||||
|
export const habitTracker = pgSchema("habit_tracker");
|
||||||
|
|
||||||
|
export const habit = habitTracker.table(
|
||||||
|
"habit",
|
||||||
|
{
|
||||||
|
id: idPrimaryKey,
|
||||||
|
userId: uuid("user_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, { onDelete: "cascade" }),
|
||||||
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
|
description: text("description"),
|
||||||
|
frequencyType: varchar("frequency_type", { length: 20 }).notNull(),
|
||||||
|
targetCount: integer("target_count").notNull().default(1),
|
||||||
|
intervalDays: integer("interval_days"),
|
||||||
|
active: boolean("active").notNull().default(true),
|
||||||
|
...timestampSchema,
|
||||||
|
},
|
||||||
|
(t) => [
|
||||||
|
check(
|
||||||
|
"freqency_type_check",
|
||||||
|
sql`${t.frequencyType} IN ('daily', 'interval', 'multi_daily')`,
|
||||||
|
),
|
||||||
|
check("target_count_check", sql`${t.targetCount} > 0`),
|
||||||
|
check(
|
||||||
|
"interval_days_check",
|
||||||
|
sql`(${t.frequencyType} = 'interval' AND ${t.intervalDays} IS NOT NULL AND ${t.intervalDays} > 0) OR (${t.frequencyType} != 'interval' AND ${t.intervalDays} IS NULL)`,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const habitCompletion = habitTracker.table("habit_completion", {
|
||||||
|
id: uuid("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||||
|
habitId: uuid("habit_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => habit.id, { onDelete: "cascade" }),
|
||||||
|
notes: text("notes"),
|
||||||
|
completed_at: timestamp("completed_at").defaultNow().notNull(),
|
||||||
|
...timestampSchema,
|
||||||
|
});
|
96
api/src/db/schema/intake.ts
Normal file
96
api/src/db/schema/intake.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
boolean,
|
||||||
|
check,
|
||||||
|
date,
|
||||||
|
decimal,
|
||||||
|
integer,
|
||||||
|
pgSchema,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
unique,
|
||||||
|
uuid,
|
||||||
|
varchar,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { user } from "./auth";
|
||||||
|
import { idPrimaryKey, timestampSchema } from "./helpers";
|
||||||
|
|
||||||
|
export const intakeTracker = pgSchema("intake_tracker");
|
||||||
|
|
||||||
|
export const intakeMetric = intakeTracker.table(
|
||||||
|
"intake_metric",
|
||||||
|
{
|
||||||
|
id: idPrimaryKey,
|
||||||
|
userId: uuid("user_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, { onDelete: "cascade" }),
|
||||||
|
metricType: varchar("metric_type", { length: 50 }).notNull(),
|
||||||
|
unit: varchar("unit", { length: 20 }).notNull(),
|
||||||
|
displayName: varchar("display_name", { length: 100 }).notNull(),
|
||||||
|
|
||||||
|
targetValue: decimal("target_value", { precision: 10, scale: 2 }),
|
||||||
|
minValue: decimal("min_value", { precision: 10, scale: 2 }),
|
||||||
|
maxValue: decimal("max_value", { precision: 10, scale: 2 }),
|
||||||
|
isCumulative: boolean("is_cumulative").notNull().default(true),
|
||||||
|
active: boolean("active").notNull().default(true),
|
||||||
|
...timestampSchema,
|
||||||
|
},
|
||||||
|
(t) => [
|
||||||
|
unique().on(t.userId, t.metricType),
|
||||||
|
check(
|
||||||
|
"positive_target_check",
|
||||||
|
sql`${t.targetValue} IS NULL OR ${t.targetValue} > 0`,
|
||||||
|
),
|
||||||
|
check(
|
||||||
|
"positive_min_check",
|
||||||
|
sql`${t.minValue} IS NULL OR ${t.minValue} >= 0`,
|
||||||
|
),
|
||||||
|
check(
|
||||||
|
"positive_max_check",
|
||||||
|
sql`${t.maxValue} IS NULL OR ${t.maxValue} >= 0`,
|
||||||
|
),
|
||||||
|
check(
|
||||||
|
"min_max_check",
|
||||||
|
sql`${t.minValue} IS NULL OR ${t.maxValue} IS NULL OR ${t.minValue} <= ${t.maxValue}`,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const intakeRecord = intakeTracker.table(
|
||||||
|
"intake_record",
|
||||||
|
{
|
||||||
|
id: idPrimaryKey,
|
||||||
|
intakeMetricId: uuid("intake_metric_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => intakeMetric.id, { onDelete: "cascade" }),
|
||||||
|
value: decimal({ precision: 10, scale: 2 }),
|
||||||
|
recordedAt: timestamp("recorded_at").notNull().defaultNow(),
|
||||||
|
notes: text("notes"),
|
||||||
|
...timestampSchema,
|
||||||
|
},
|
||||||
|
(t) => [
|
||||||
|
check("positive_value_check", sql`${t.value} > 0`),
|
||||||
|
check("recorded_at_not_future_check", sql`${t.recordedAt} <= NOW()`),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const dailySummary = intakeTracker.table(
|
||||||
|
"daily_summary",
|
||||||
|
{
|
||||||
|
id: idPrimaryKey,
|
||||||
|
intakeMetricId: uuid("intake_metric_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => intakeMetric.id, { onDelete: "cascade" }),
|
||||||
|
date: date("date").notNull(),
|
||||||
|
totalValue: decimal("total_value", { precision: 10, scale: 2 }).notNull(),
|
||||||
|
entryCount: integer("entry_count").notNull(),
|
||||||
|
firstEntryAt: timestamp("first_entry_at"),
|
||||||
|
lastEntryAt: timestamp("last_entry_at"),
|
||||||
|
...timestampSchema,
|
||||||
|
},
|
||||||
|
(t) => [
|
||||||
|
unique().on(t.intakeMetricId, t.date),
|
||||||
|
check("positive_total_check", sql`${t.totalValue} > 0`),
|
||||||
|
check("positive_count_check", sql`${t.entryCount} > 0`),
|
||||||
|
],
|
||||||
|
);
|
Loading…
Reference in New Issue
Block a user