feat(domain): complete phase 2 backend core
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
CREATE TABLE `calendar_events` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`client_id` text,
|
||||
`project_id` text,
|
||||
`task_id` text,
|
||||
`title` text NOT NULL,
|
||||
`description` text,
|
||||
`type` text DEFAULT 'focus' NOT NULL,
|
||||
`starts_at` integer NOT NULL,
|
||||
`ends_at` integer,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
FOREIGN KEY (`task_id`) REFERENCES `tasks`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
CONSTRAINT "calendar_events_type_check" CHECK("calendar_events"."type" in ('meeting', 'focus', 'deadline', 'personal', 'finance')),
|
||||
CONSTRAINT "calendar_events_time_check" CHECK("calendar_events"."ends_at" is null or "calendar_events"."ends_at" >= "calendar_events"."starts_at")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `calendar_events_owner_range_idx` ON `calendar_events` (`owner_user_id`,`starts_at`);--> statement-breakpoint
|
||||
CREATE INDEX `calendar_events_project_id_idx` ON `calendar_events` (`project_id`);--> statement-breakpoint
|
||||
CREATE INDEX `calendar_events_task_id_idx` ON `calendar_events` (`task_id`);--> statement-breakpoint
|
||||
CREATE TABLE `chat_messages` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`session_id` text NOT NULL,
|
||||
`role` text NOT NULL,
|
||||
`content` text NOT NULL,
|
||||
`context_journal_entry_ids` text DEFAULT '[]' NOT NULL,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`session_id`) REFERENCES `chat_sessions`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
CONSTRAINT "chat_messages_role_check" CHECK("chat_messages"."role" in ('system', 'user', 'assistant', 'tool'))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `chat_messages_session_created_idx` ON `chat_messages` (`session_id`,`created_at`);--> statement-breakpoint
|
||||
CREATE TABLE `chat_sessions` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`title` text NOT NULL,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `chat_sessions_owner_updated_idx` ON `chat_sessions` (`owner_user_id`,`updated_at`);--> statement-breakpoint
|
||||
CREATE TABLE `client_activities` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`client_id` text NOT NULL,
|
||||
`type` text NOT NULL,
|
||||
`title` text NOT NULL,
|
||||
`content` text,
|
||||
`activity_date` integer NOT NULL,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
CONSTRAINT "client_activities_type_check" CHECK("client_activities"."type" in ('note', 'call', 'meeting', 'email'))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `client_activities_owner_client_idx` ON `client_activities` (`owner_user_id`,`client_id`);--> statement-breakpoint
|
||||
CREATE INDEX `client_activities_client_date_idx` ON `client_activities` (`client_id`,`activity_date`);--> statement-breakpoint
|
||||
CREATE TABLE `clients` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`auth_user_id` text,
|
||||
`name` text NOT NULL,
|
||||
`company_name` text,
|
||||
`email` text,
|
||||
`phone` text,
|
||||
`website` text,
|
||||
`status` text DEFAULT 'active' NOT NULL,
|
||||
`pipeline_stage` text DEFAULT 'lead' NOT NULL,
|
||||
`next_follow_up_date` text,
|
||||
`notes` text,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`auth_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
CONSTRAINT "clients_status_check" CHECK("clients"."status" in ('active', 'paused', 'archived')),
|
||||
CONSTRAINT "clients_pipeline_stage_check" CHECK("clients"."pipeline_stage" in ('lead', 'contacted', 'proposal_sent', 'won', 'lost'))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `clients_auth_user_id_unique` ON `clients` (`auth_user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `clients_owner_user_id_idx` ON `clients` (`owner_user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `clients_owner_status_idx` ON `clients` (`owner_user_id`,`status`);--> statement-breakpoint
|
||||
CREATE INDEX `clients_owner_pipeline_idx` ON `clients` (`owner_user_id`,`pipeline_stage`);--> statement-breakpoint
|
||||
CREATE INDEX `clients_next_follow_up_date_idx` ON `clients` (`next_follow_up_date`);--> statement-breakpoint
|
||||
CREATE TABLE `contracts` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`proposal_id` text,
|
||||
`client_id` text,
|
||||
`title` text NOT NULL,
|
||||
`content` text,
|
||||
`status` text DEFAULT 'draft' NOT NULL,
|
||||
`signed_at` integer,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`proposal_id`) REFERENCES `proposals`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
CONSTRAINT "contracts_status_check" CHECK("contracts"."status" in ('draft', 'active', 'completed', 'cancelled'))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `contracts_owner_status_idx` ON `contracts` (`owner_user_id`,`status`);--> statement-breakpoint
|
||||
CREATE TABLE `finance_transactions` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`client_id` text,
|
||||
`project_id` text,
|
||||
`type` text NOT NULL,
|
||||
`amount_minor` integer NOT NULL,
|
||||
`currency` text DEFAULT 'USD' NOT NULL,
|
||||
`transaction_date` text NOT NULL,
|
||||
`category` text,
|
||||
`payment_status` text DEFAULT 'planned' NOT NULL,
|
||||
`description` text,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
CONSTRAINT "finance_transactions_type_check" CHECK("finance_transactions"."type" in ('income', 'expense')),
|
||||
CONSTRAINT "finance_transactions_amount_check" CHECK("finance_transactions"."amount_minor" >= 0),
|
||||
CONSTRAINT "finance_transactions_payment_status_check" CHECK("finance_transactions"."payment_status" in ('planned', 'pending', 'paid', 'cancelled')),
|
||||
CONSTRAINT "finance_transactions_currency_check" CHECK(length("finance_transactions"."currency") = 3)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `finance_transactions_owner_date_idx` ON `finance_transactions` (`owner_user_id`,`transaction_date`);--> statement-breakpoint
|
||||
CREATE INDEX `finance_transactions_owner_type_idx` ON `finance_transactions` (`owner_user_id`,`type`);--> statement-breakpoint
|
||||
CREATE INDEX `finance_transactions_client_id_idx` ON `finance_transactions` (`client_id`);--> statement-breakpoint
|
||||
CREATE INDEX `finance_transactions_project_id_idx` ON `finance_transactions` (`project_id`);--> statement-breakpoint
|
||||
CREATE TABLE `invoices` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`client_id` text,
|
||||
`project_id` text,
|
||||
`invoice_number` text NOT NULL,
|
||||
`amount_minor` integer DEFAULT 0 NOT NULL,
|
||||
`tax_basis_points` integer DEFAULT 0 NOT NULL,
|
||||
`currency` text DEFAULT 'TRY' NOT NULL,
|
||||
`status` text DEFAULT 'draft' NOT NULL,
|
||||
`issue_date` text NOT NULL,
|
||||
`due_date` text,
|
||||
`paid_at` integer,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
CONSTRAINT "invoices_status_check" CHECK("invoices"."status" in ('draft', 'sent', 'paid', 'overdue', 'cancelled')),
|
||||
CONSTRAINT "invoices_amount_check" CHECK("invoices"."amount_minor" >= 0),
|
||||
CONSTRAINT "invoices_tax_check" CHECK("invoices"."tax_basis_points" between 0 and 10000)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `invoices_owner_number_unique` ON `invoices` (`owner_user_id`,`invoice_number`);--> statement-breakpoint
|
||||
CREATE INDEX `invoices_owner_status_idx` ON `invoices` (`owner_user_id`,`status`);--> statement-breakpoint
|
||||
CREATE TABLE `journal_entries` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`entry_date` text NOT NULL,
|
||||
`mood_score` integer,
|
||||
`energy_score` integer,
|
||||
`work_satisfaction_score` integer,
|
||||
`mood_label` text,
|
||||
`note` text,
|
||||
`legacy_ai_metadata` text,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
CONSTRAINT "journal_entries_mood_score_check" CHECK("journal_entries"."mood_score" is null or "journal_entries"."mood_score" between 1 and 5),
|
||||
CONSTRAINT "journal_entries_energy_score_check" CHECK("journal_entries"."energy_score" is null or "journal_entries"."energy_score" between 1 and 5),
|
||||
CONSTRAINT "journal_entries_work_score_check" CHECK("journal_entries"."work_satisfaction_score" is null or "journal_entries"."work_satisfaction_score" between 1 and 5)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `journal_entries_owner_date_unique` ON `journal_entries` (`owner_user_id`,`entry_date`);--> statement-breakpoint
|
||||
CREATE INDEX `journal_entries_owner_date_idx` ON `journal_entries` (`owner_user_id`,`entry_date`);--> statement-breakpoint
|
||||
CREATE TABLE `project_planning_sections` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`project_id` text NOT NULL,
|
||||
`category` text NOT NULL,
|
||||
`title` text NOT NULL,
|
||||
`content` text,
|
||||
`metadata` text DEFAULT '{}' NOT NULL,
|
||||
`sort_order` integer DEFAULT 0 NOT NULL,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
CONSTRAINT "project_planning_sections_category_check" CHECK("project_planning_sections"."category" in ('overview', 'problem', 'goal', 'audience', 'scope', 'design_system', 'color_palette', 'typography', 'assets', 'notes'))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `project_planning_sections_owner_idx` ON `project_planning_sections` (`owner_user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `project_planning_sections_project_order_idx` ON `project_planning_sections` (`project_id`,`sort_order`);--> statement-breakpoint
|
||||
CREATE TABLE `project_revisions` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`project_id` text NOT NULL,
|
||||
`client_id` text NOT NULL,
|
||||
`requested_by_user_id` text NOT NULL,
|
||||
`description` text NOT NULL,
|
||||
`status` text DEFAULT 'pending' NOT NULL,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`requested_by_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
CONSTRAINT "project_revisions_status_check" CHECK("project_revisions"."status" in ('pending', 'in_progress', 'completed', 'rejected'))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `project_revisions_owner_project_idx` ON `project_revisions` (`owner_user_id`,`project_id`);--> statement-breakpoint
|
||||
CREATE INDEX `project_revisions_client_project_idx` ON `project_revisions` (`client_id`,`project_id`);--> statement-breakpoint
|
||||
CREATE TABLE `projects` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`client_id` text,
|
||||
`name` text NOT NULL,
|
||||
`type` text DEFAULT 'client_project' NOT NULL,
|
||||
`description` text,
|
||||
`status` text DEFAULT 'planning' NOT NULL,
|
||||
`start_date` text,
|
||||
`due_date` text,
|
||||
`budget_amount_minor` integer,
|
||||
`currency` text DEFAULT 'USD' NOT NULL,
|
||||
`progress` integer DEFAULT 0 NOT NULL,
|
||||
`progress_type` text DEFAULT 'manual' NOT NULL,
|
||||
`revision_quota` integer DEFAULT 0 NOT NULL,
|
||||
`legacy_cover_image_path` text,
|
||||
`cover_image_alt` text,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
CONSTRAINT "projects_type_check" CHECK("projects"."type" in ('client_project', 'side_project')),
|
||||
CONSTRAINT "projects_status_check" CHECK("projects"."status" in ('planning', 'active', 'paused', 'completed', 'cancelled')),
|
||||
CONSTRAINT "projects_progress_check" CHECK("projects"."progress" between 0 and 100),
|
||||
CONSTRAINT "projects_revision_quota_check" CHECK("projects"."revision_quota" >= 0),
|
||||
CONSTRAINT "projects_budget_check" CHECK("projects"."budget_amount_minor" is null or "projects"."budget_amount_minor" >= 0),
|
||||
CONSTRAINT "projects_currency_check" CHECK(length("projects"."currency") = 3)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `projects_owner_user_id_idx` ON `projects` (`owner_user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `projects_owner_status_idx` ON `projects` (`owner_user_id`,`status`);--> statement-breakpoint
|
||||
CREATE INDEX `projects_client_id_idx` ON `projects` (`client_id`);--> statement-breakpoint
|
||||
CREATE INDEX `projects_due_date_idx` ON `projects` (`due_date`);--> statement-breakpoint
|
||||
CREATE TABLE `proposals` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`client_id` text,
|
||||
`project_id` text,
|
||||
`title` text NOT NULL,
|
||||
`description` text,
|
||||
`amount_minor` integer DEFAULT 0 NOT NULL,
|
||||
`currency` text DEFAULT 'TRY' NOT NULL,
|
||||
`status` text DEFAULT 'draft' NOT NULL,
|
||||
`valid_until` integer,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
CONSTRAINT "proposals_status_check" CHECK("proposals"."status" in ('draft', 'sent', 'accepted', 'rejected')),
|
||||
CONSTRAINT "proposals_amount_check" CHECK("proposals"."amount_minor" >= 0)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `proposals_owner_status_idx` ON `proposals` (`owner_user_id`,`status`);--> statement-breakpoint
|
||||
CREATE TABLE `subscriptions` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`amount_minor` integer DEFAULT 0 NOT NULL,
|
||||
`currency` text DEFAULT 'TRY' NOT NULL,
|
||||
`billing_cycle` text DEFAULT 'monthly' NOT NULL,
|
||||
`next_billing_date` text,
|
||||
`status` text DEFAULT 'active' NOT NULL,
|
||||
`category` text,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
CONSTRAINT "subscriptions_cycle_check" CHECK("subscriptions"."billing_cycle" in ('weekly', 'monthly', 'yearly')),
|
||||
CONSTRAINT "subscriptions_status_check" CHECK("subscriptions"."status" in ('active', 'cancelled')),
|
||||
CONSTRAINT "subscriptions_amount_check" CHECK("subscriptions"."amount_minor" >= 0)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `subscriptions_owner_status_idx` ON `subscriptions` (`owner_user_id`,`status`);--> statement-breakpoint
|
||||
CREATE INDEX `subscriptions_next_billing_date_idx` ON `subscriptions` (`next_billing_date`);--> statement-breakpoint
|
||||
CREATE TABLE `tasks` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`owner_user_id` text NOT NULL,
|
||||
`client_id` text,
|
||||
`project_id` text,
|
||||
`source_journal_entry_id` text,
|
||||
`title` text NOT NULL,
|
||||
`description` text,
|
||||
`status` text DEFAULT 'todo' NOT NULL,
|
||||
`priority` text DEFAULT 'medium' NOT NULL,
|
||||
`scheduled_date` text,
|
||||
`due_at` integer,
|
||||
`estimated_minutes` integer,
|
||||
`actual_minutes` integer,
|
||||
`ai_generated` integer DEFAULT false NOT NULL,
|
||||
`is_public_to_client` integer DEFAULT false NOT NULL,
|
||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||
FOREIGN KEY (`owner_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
FOREIGN KEY (`source_journal_entry_id`) REFERENCES `journal_entries`(`id`) ON UPDATE no action ON DELETE set null,
|
||||
CONSTRAINT "tasks_status_check" CHECK("tasks"."status" in ('todo', 'in_progress', 'done', 'cancelled')),
|
||||
CONSTRAINT "tasks_priority_check" CHECK("tasks"."priority" in ('low', 'medium', 'high', 'urgent')),
|
||||
CONSTRAINT "tasks_estimated_minutes_check" CHECK("tasks"."estimated_minutes" is null or "tasks"."estimated_minutes" >= 0),
|
||||
CONSTRAINT "tasks_actual_minutes_check" CHECK("tasks"."actual_minutes" is null or "tasks"."actual_minutes" >= 0)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `tasks_owner_user_id_idx` ON `tasks` (`owner_user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `tasks_owner_status_idx` ON `tasks` (`owner_user_id`,`status`);--> statement-breakpoint
|
||||
CREATE INDEX `tasks_project_id_idx` ON `tasks` (`project_id`);--> statement-breakpoint
|
||||
CREATE INDEX `tasks_client_id_idx` ON `tasks` (`client_id`);--> statement-breakpoint
|
||||
CREATE INDEX `tasks_due_at_idx` ON `tasks` (`due_at`);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,13 @@
|
||||
"when": 1784205329112,
|
||||
"tag": "0002_mighty_korg",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "6",
|
||||
"when": 1784208712933,
|
||||
"tag": "0003_chief_excalibur",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
PortalInvitationStatus,
|
||||
SetupStatus,
|
||||
UserRole,
|
||||
} from "@/server/auth/types";
|
||||
} from "../../auth/types";
|
||||
|
||||
const nowMs = sql`(cast(unixepoch('subsecond') * 1000 as integer))`;
|
||||
|
||||
|
||||
@@ -0,0 +1,474 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import {
|
||||
check,
|
||||
index,
|
||||
integer,
|
||||
sqliteTable,
|
||||
text,
|
||||
uniqueIndex,
|
||||
} from "drizzle-orm/sqlite-core";
|
||||
import {
|
||||
type CalendarEventType,
|
||||
type ChatMessageRole,
|
||||
type ClientActivityType,
|
||||
type ClientPipelineStage,
|
||||
type ClientStatus,
|
||||
type ContractStatus,
|
||||
type FinanceTransactionType,
|
||||
type InvoiceStatus,
|
||||
type PaymentStatus,
|
||||
type PlanningSectionCategory,
|
||||
type ProjectProgressType,
|
||||
type ProjectStatus,
|
||||
type ProjectType,
|
||||
type ProposalStatus,
|
||||
type RevisionStatus,
|
||||
type SubscriptionBillingCycle,
|
||||
type SubscriptionStatus,
|
||||
type TaskPriority,
|
||||
type TaskStatus,
|
||||
} from "../../domain/types";
|
||||
import { user } from "./auth";
|
||||
|
||||
const nowMs = sql`(cast(unixepoch('subsecond') * 1000 as integer))`;
|
||||
|
||||
export const clients = sqliteTable(
|
||||
"clients",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
authUserId: text("auth_user_id").references(() => user.id, { onDelete: "set null" }),
|
||||
name: text("name").notNull(),
|
||||
companyName: text("company_name"),
|
||||
email: text("email"),
|
||||
phone: text("phone"),
|
||||
website: text("website"),
|
||||
status: text("status").$type<ClientStatus>().default("active").notNull(),
|
||||
pipelineStage: text("pipeline_stage").$type<ClientPipelineStage>().default("lead").notNull(),
|
||||
nextFollowUpDate: text("next_follow_up_date"),
|
||||
notes: text("notes"),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
||||
.default(nowMs)
|
||||
.$onUpdate(() => new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex("clients_auth_user_id_unique").on(table.authUserId),
|
||||
index("clients_owner_user_id_idx").on(table.ownerUserId),
|
||||
index("clients_owner_status_idx").on(table.ownerUserId, table.status),
|
||||
index("clients_owner_pipeline_idx").on(table.ownerUserId, table.pipelineStage),
|
||||
index("clients_next_follow_up_date_idx").on(table.nextFollowUpDate),
|
||||
check("clients_status_check", sql`${table.status} in ('active', 'paused', 'archived')`),
|
||||
check(
|
||||
"clients_pipeline_stage_check",
|
||||
sql`${table.pipelineStage} in ('lead', 'contacted', 'proposal_sent', 'won', 'lost')`,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
export const projects = sqliteTable(
|
||||
"projects",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
clientId: text("client_id").references(() => clients.id, { onDelete: "set null" }),
|
||||
name: text("name").notNull(),
|
||||
type: text("type").$type<ProjectType>().default("client_project").notNull(),
|
||||
description: text("description"),
|
||||
status: text("status").$type<ProjectStatus>().default("planning").notNull(),
|
||||
startDate: text("start_date"),
|
||||
dueDate: text("due_date"),
|
||||
budgetAmountMinor: integer("budget_amount_minor"),
|
||||
currency: text("currency").default("USD").notNull(),
|
||||
progress: integer("progress").default(0).notNull(),
|
||||
progressType: text("progress_type").$type<ProjectProgressType>().default("manual").notNull(),
|
||||
revisionQuota: integer("revision_quota").default(0).notNull(),
|
||||
legacyCoverImagePath: text("legacy_cover_image_path"),
|
||||
coverImageAlt: text("cover_image_alt"),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
||||
.default(nowMs)
|
||||
.$onUpdate(() => new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("projects_owner_user_id_idx").on(table.ownerUserId),
|
||||
index("projects_owner_status_idx").on(table.ownerUserId, table.status),
|
||||
index("projects_client_id_idx").on(table.clientId),
|
||||
index("projects_due_date_idx").on(table.dueDate),
|
||||
check("projects_type_check", sql`${table.type} in ('client_project', 'side_project')`),
|
||||
check(
|
||||
"projects_status_check",
|
||||
sql`${table.status} in ('planning', 'active', 'paused', 'completed', 'cancelled')`,
|
||||
),
|
||||
check("projects_progress_check", sql`${table.progress} between 0 and 100`),
|
||||
check("projects_revision_quota_check", sql`${table.revisionQuota} >= 0`),
|
||||
check("projects_budget_check", sql`${table.budgetAmountMinor} is null or ${table.budgetAmountMinor} >= 0`),
|
||||
check("projects_currency_check", sql`length(${table.currency}) = 3`),
|
||||
],
|
||||
);
|
||||
|
||||
export const journalEntries = sqliteTable(
|
||||
"journal_entries",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
entryDate: text("entry_date").notNull(),
|
||||
moodScore: integer("mood_score"),
|
||||
energyScore: integer("energy_score"),
|
||||
workSatisfactionScore: integer("work_satisfaction_score"),
|
||||
moodLabel: text("mood_label"),
|
||||
note: text("note"),
|
||||
legacyAiMetadata: text("legacy_ai_metadata", { mode: "json" }).$type<Record<string, unknown> | null>(),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
||||
.default(nowMs)
|
||||
.$onUpdate(() => new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex("journal_entries_owner_date_unique").on(table.ownerUserId, table.entryDate),
|
||||
index("journal_entries_owner_date_idx").on(table.ownerUserId, table.entryDate),
|
||||
check("journal_entries_mood_score_check", sql`${table.moodScore} is null or ${table.moodScore} between 1 and 5`),
|
||||
check("journal_entries_energy_score_check", sql`${table.energyScore} is null or ${table.energyScore} between 1 and 5`),
|
||||
check(
|
||||
"journal_entries_work_score_check",
|
||||
sql`${table.workSatisfactionScore} is null or ${table.workSatisfactionScore} between 1 and 5`,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
export const tasks = sqliteTable(
|
||||
"tasks",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
clientId: text("client_id").references(() => clients.id, { onDelete: "set null" }),
|
||||
projectId: text("project_id").references(() => projects.id, { onDelete: "set null" }),
|
||||
sourceJournalEntryId: text("source_journal_entry_id").references(() => journalEntries.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
status: text("status").$type<TaskStatus>().default("todo").notNull(),
|
||||
priority: text("priority").$type<TaskPriority>().default("medium").notNull(),
|
||||
scheduledDate: text("scheduled_date"),
|
||||
dueAt: integer("due_at", { mode: "timestamp_ms" }),
|
||||
estimatedMinutes: integer("estimated_minutes"),
|
||||
actualMinutes: integer("actual_minutes"),
|
||||
aiGenerated: integer("ai_generated", { mode: "boolean" }).default(false).notNull(),
|
||||
isPublicToClient: integer("is_public_to_client", { mode: "boolean" }).default(false).notNull(),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
||||
.default(nowMs)
|
||||
.$onUpdate(() => new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("tasks_owner_user_id_idx").on(table.ownerUserId),
|
||||
index("tasks_owner_status_idx").on(table.ownerUserId, table.status),
|
||||
index("tasks_project_id_idx").on(table.projectId),
|
||||
index("tasks_client_id_idx").on(table.clientId),
|
||||
index("tasks_due_at_idx").on(table.dueAt),
|
||||
check("tasks_status_check", sql`${table.status} in ('todo', 'in_progress', 'done', 'cancelled')`),
|
||||
check("tasks_priority_check", sql`${table.priority} in ('low', 'medium', 'high', 'urgent')`),
|
||||
check("tasks_estimated_minutes_check", sql`${table.estimatedMinutes} is null or ${table.estimatedMinutes} >= 0`),
|
||||
check("tasks_actual_minutes_check", sql`${table.actualMinutes} is null or ${table.actualMinutes} >= 0`),
|
||||
],
|
||||
);
|
||||
|
||||
export const calendarEvents = sqliteTable(
|
||||
"calendar_events",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
clientId: text("client_id").references(() => clients.id, { onDelete: "set null" }),
|
||||
projectId: text("project_id").references(() => projects.id, { onDelete: "set null" }),
|
||||
taskId: text("task_id").references(() => tasks.id, { onDelete: "set null" }),
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
type: text("type").$type<CalendarEventType>().default("focus").notNull(),
|
||||
startsAt: integer("starts_at", { mode: "timestamp_ms" }).notNull(),
|
||||
endsAt: integer("ends_at", { mode: "timestamp_ms" }),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
||||
.default(nowMs)
|
||||
.$onUpdate(() => new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("calendar_events_owner_range_idx").on(table.ownerUserId, table.startsAt),
|
||||
index("calendar_events_project_id_idx").on(table.projectId),
|
||||
index("calendar_events_task_id_idx").on(table.taskId),
|
||||
check("calendar_events_type_check", sql`${table.type} in ('meeting', 'focus', 'deadline', 'personal', 'finance')`),
|
||||
check("calendar_events_time_check", sql`${table.endsAt} is null or ${table.endsAt} >= ${table.startsAt}`),
|
||||
],
|
||||
);
|
||||
|
||||
export const financeTransactions = sqliteTable(
|
||||
"finance_transactions",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
clientId: text("client_id").references(() => clients.id, { onDelete: "set null" }),
|
||||
projectId: text("project_id").references(() => projects.id, { onDelete: "set null" }),
|
||||
type: text("type").$type<FinanceTransactionType>().notNull(),
|
||||
amountMinor: integer("amount_minor").notNull(),
|
||||
currency: text("currency").default("USD").notNull(),
|
||||
transactionDate: text("transaction_date").notNull(),
|
||||
category: text("category"),
|
||||
paymentStatus: text("payment_status").$type<PaymentStatus>().default("planned").notNull(),
|
||||
description: text("description"),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
||||
.default(nowMs)
|
||||
.$onUpdate(() => new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("finance_transactions_owner_date_idx").on(table.ownerUserId, table.transactionDate),
|
||||
index("finance_transactions_owner_type_idx").on(table.ownerUserId, table.type),
|
||||
index("finance_transactions_client_id_idx").on(table.clientId),
|
||||
index("finance_transactions_project_id_idx").on(table.projectId),
|
||||
check("finance_transactions_type_check", sql`${table.type} in ('income', 'expense')`),
|
||||
check("finance_transactions_amount_check", sql`${table.amountMinor} >= 0`),
|
||||
check(
|
||||
"finance_transactions_payment_status_check",
|
||||
sql`${table.paymentStatus} in ('planned', 'pending', 'paid', 'cancelled')`,
|
||||
),
|
||||
check("finance_transactions_currency_check", sql`length(${table.currency}) = 3`),
|
||||
],
|
||||
);
|
||||
|
||||
export const clientActivities = sqliteTable(
|
||||
"client_activities",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
clientId: text("client_id")
|
||||
.notNull()
|
||||
.references(() => clients.id, { onDelete: "cascade" }),
|
||||
type: text("type").$type<ClientActivityType>().notNull(),
|
||||
title: text("title").notNull(),
|
||||
content: text("content"),
|
||||
activityDate: integer("activity_date", { mode: "timestamp_ms" }).notNull(),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("client_activities_owner_client_idx").on(table.ownerUserId, table.clientId),
|
||||
index("client_activities_client_date_idx").on(table.clientId, table.activityDate),
|
||||
check("client_activities_type_check", sql`${table.type} in ('note', 'call', 'meeting', 'email')`),
|
||||
],
|
||||
);
|
||||
|
||||
export const projectPlanningSections = sqliteTable(
|
||||
"project_planning_sections",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
projectId: text("project_id")
|
||||
.notNull()
|
||||
.references(() => projects.id, { onDelete: "cascade" }),
|
||||
category: text("category").$type<PlanningSectionCategory>().notNull(),
|
||||
title: text("title").notNull(),
|
||||
content: text("content"),
|
||||
metadata: text("metadata", { mode: "json" }).$type<Record<string, unknown>>().default({}).notNull(),
|
||||
sortOrder: integer("sort_order").default(0).notNull(),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
||||
.default(nowMs)
|
||||
.$onUpdate(() => new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("project_planning_sections_owner_idx").on(table.ownerUserId),
|
||||
index("project_planning_sections_project_order_idx").on(table.projectId, table.sortOrder),
|
||||
check(
|
||||
"project_planning_sections_category_check",
|
||||
sql`${table.category} in ('overview', 'problem', 'goal', 'audience', 'scope', 'design_system', 'color_palette', 'typography', 'assets', 'notes')`,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
export const projectRevisions = sqliteTable(
|
||||
"project_revisions",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
projectId: text("project_id")
|
||||
.notNull()
|
||||
.references(() => projects.id, { onDelete: "cascade" }),
|
||||
clientId: text("client_id")
|
||||
.notNull()
|
||||
.references(() => clients.id, { onDelete: "cascade" }),
|
||||
requestedByUserId: text("requested_by_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
description: text("description").notNull(),
|
||||
status: text("status").$type<RevisionStatus>().default("pending").notNull(),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
||||
.default(nowMs)
|
||||
.$onUpdate(() => new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("project_revisions_owner_project_idx").on(table.ownerUserId, table.projectId),
|
||||
index("project_revisions_client_project_idx").on(table.clientId, table.projectId),
|
||||
check(
|
||||
"project_revisions_status_check",
|
||||
sql`${table.status} in ('pending', 'in_progress', 'completed', 'rejected')`,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
export const chatSessions = sqliteTable(
|
||||
"chat_sessions",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
title: text("title").notNull(),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
||||
.default(nowMs)
|
||||
.$onUpdate(() => new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [index("chat_sessions_owner_updated_idx").on(table.ownerUserId, table.updatedAt)],
|
||||
);
|
||||
|
||||
export const chatMessages = sqliteTable(
|
||||
"chat_messages",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
sessionId: text("session_id")
|
||||
.notNull()
|
||||
.references(() => chatSessions.id, { onDelete: "cascade" }),
|
||||
role: text("role").$type<ChatMessageRole>().notNull(),
|
||||
content: text("content").notNull(),
|
||||
contextJournalEntryIds: text("context_journal_entry_ids", { mode: "json" })
|
||||
.$type<string[]>()
|
||||
.default([])
|
||||
.notNull(),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("chat_messages_session_created_idx").on(table.sessionId, table.createdAt),
|
||||
check("chat_messages_role_check", sql`${table.role} in ('system', 'user', 'assistant', 'tool')`),
|
||||
],
|
||||
);
|
||||
|
||||
export const proposals = sqliteTable(
|
||||
"proposals",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
||||
clientId: text("client_id").references(() => clients.id, { onDelete: "set null" }),
|
||||
projectId: text("project_id").references(() => projects.id, { onDelete: "set null" }),
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
amountMinor: integer("amount_minor").default(0).notNull(),
|
||||
currency: text("currency").default("TRY").notNull(),
|
||||
status: text("status").$type<ProposalStatus>().default("draft").notNull(),
|
||||
validUntil: integer("valid_until", { mode: "timestamp_ms" }),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("proposals_owner_status_idx").on(table.ownerUserId, table.status),
|
||||
check("proposals_status_check", sql`${table.status} in ('draft', 'sent', 'accepted', 'rejected')`),
|
||||
check("proposals_amount_check", sql`${table.amountMinor} >= 0`),
|
||||
],
|
||||
);
|
||||
|
||||
export const contracts = sqliteTable(
|
||||
"contracts",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
||||
proposalId: text("proposal_id").references(() => proposals.id, { onDelete: "set null" }),
|
||||
clientId: text("client_id").references(() => clients.id, { onDelete: "set null" }),
|
||||
title: text("title").notNull(),
|
||||
content: text("content"),
|
||||
status: text("status").$type<ContractStatus>().default("draft").notNull(),
|
||||
signedAt: integer("signed_at", { mode: "timestamp_ms" }),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("contracts_owner_status_idx").on(table.ownerUserId, table.status),
|
||||
check("contracts_status_check", sql`${table.status} in ('draft', 'active', 'completed', 'cancelled')`),
|
||||
],
|
||||
);
|
||||
|
||||
export const invoices = sqliteTable(
|
||||
"invoices",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
||||
clientId: text("client_id").references(() => clients.id, { onDelete: "set null" }),
|
||||
projectId: text("project_id").references(() => projects.id, { onDelete: "set null" }),
|
||||
invoiceNumber: text("invoice_number").notNull(),
|
||||
amountMinor: integer("amount_minor").default(0).notNull(),
|
||||
taxBasisPoints: integer("tax_basis_points").default(0).notNull(),
|
||||
currency: text("currency").default("TRY").notNull(),
|
||||
status: text("status").$type<InvoiceStatus>().default("draft").notNull(),
|
||||
issueDate: text("issue_date").notNull(),
|
||||
dueDate: text("due_date"),
|
||||
paidAt: integer("paid_at", { mode: "timestamp_ms" }),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex("invoices_owner_number_unique").on(table.ownerUserId, table.invoiceNumber),
|
||||
index("invoices_owner_status_idx").on(table.ownerUserId, table.status),
|
||||
check("invoices_status_check", sql`${table.status} in ('draft', 'sent', 'paid', 'overdue', 'cancelled')`),
|
||||
check("invoices_amount_check", sql`${table.amountMinor} >= 0`),
|
||||
check("invoices_tax_check", sql`${table.taxBasisPoints} between 0 and 10000`),
|
||||
],
|
||||
);
|
||||
|
||||
export const subscriptions = sqliteTable(
|
||||
"subscriptions",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
ownerUserId: text("owner_user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
||||
name: text("name").notNull(),
|
||||
amountMinor: integer("amount_minor").default(0).notNull(),
|
||||
currency: text("currency").default("TRY").notNull(),
|
||||
billingCycle: text("billing_cycle").$type<SubscriptionBillingCycle>().default("monthly").notNull(),
|
||||
nextBillingDate: text("next_billing_date"),
|
||||
status: text("status").$type<SubscriptionStatus>().default("active").notNull(),
|
||||
category: text("category"),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("subscriptions_owner_status_idx").on(table.ownerUserId, table.status),
|
||||
index("subscriptions_next_billing_date_idx").on(table.nextBillingDate),
|
||||
check("subscriptions_cycle_check", sql`${table.billingCycle} in ('weekly', 'monthly', 'yearly')`),
|
||||
check("subscriptions_status_check", sql`${table.status} in ('active', 'cancelled')`),
|
||||
check("subscriptions_amount_check", sql`${table.amountMinor} >= 0`),
|
||||
],
|
||||
);
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./auth";
|
||||
export * from "./domain";
|
||||
export * from "./runtime";
|
||||
|
||||
Reference in New Issue
Block a user