diff --git a/apps/api/prisma/migrations/20260208000000_add_missing_tables/migration.sql b/apps/api/prisma/migrations/20260208000000_add_missing_tables/migration.sql new file mode 100644 index 0000000..26f6d58 --- /dev/null +++ b/apps/api/prisma/migrations/20260208000000_add_missing_tables/migration.sql @@ -0,0 +1,162 @@ +-- CreateTable +CREATE TABLE "cron_schedules" ( + "id" UUID NOT NULL, + "workspace_id" UUID NOT NULL, + "expression" TEXT NOT NULL, + "command" TEXT NOT NULL, + "enabled" BOOLEAN NOT NULL DEFAULT true, + "last_run" TIMESTAMPTZ, + "next_run" TIMESTAMPTZ, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ NOT NULL, + + CONSTRAINT "cron_schedules_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "workspace_llm_settings" ( + "id" UUID NOT NULL, + "workspace_id" UUID NOT NULL, + "default_llm_provider_id" UUID, + "default_personality_id" UUID, + "settings" JSONB DEFAULT '{}', + "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ NOT NULL, + + CONSTRAINT "workspace_llm_settings_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "quality_gates" ( + "id" UUID NOT NULL, + "workspace_id" UUID NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "type" TEXT NOT NULL, + "command" TEXT, + "expected_output" TEXT, + "is_regex" BOOLEAN NOT NULL DEFAULT false, + "required" BOOLEAN NOT NULL DEFAULT true, + "order" INTEGER NOT NULL DEFAULT 0, + "is_enabled" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ NOT NULL, + + CONSTRAINT "quality_gates_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "task_rejections" ( + "id" UUID NOT NULL, + "task_id" TEXT NOT NULL, + "workspace_id" TEXT NOT NULL, + "agent_id" TEXT NOT NULL, + "attempt_count" INTEGER NOT NULL, + "failures" JSONB NOT NULL, + "original_task" TEXT NOT NULL, + "started_at" TIMESTAMPTZ NOT NULL, + "rejected_at" TIMESTAMPTZ NOT NULL, + "escalated" BOOLEAN NOT NULL DEFAULT false, + "manual_review" BOOLEAN NOT NULL DEFAULT false, + "resolved_at" TIMESTAMPTZ, + "resolution" TEXT, + + CONSTRAINT "task_rejections_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "token_budgets" ( + "id" UUID NOT NULL, + "task_id" UUID NOT NULL, + "workspace_id" UUID NOT NULL, + "agent_id" TEXT NOT NULL, + "allocated_tokens" INTEGER NOT NULL, + "estimated_complexity" TEXT NOT NULL, + "input_tokens_used" INTEGER NOT NULL DEFAULT 0, + "output_tokens_used" INTEGER NOT NULL DEFAULT 0, + "total_tokens_used" INTEGER NOT NULL DEFAULT 0, + "estimated_cost" DECIMAL(10,6), + "started_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + "last_updated_at" TIMESTAMPTZ NOT NULL, + "completed_at" TIMESTAMPTZ, + "budget_utilization" DOUBLE PRECISION, + "suspicious_pattern" BOOLEAN NOT NULL DEFAULT false, + "suspicious_reason" TEXT, + + CONSTRAINT "token_budgets_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "llm_usage_logs" ( + "id" UUID NOT NULL, + "workspace_id" UUID NOT NULL, + "user_id" UUID NOT NULL, + "provider" VARCHAR(50) NOT NULL, + "model" VARCHAR(100) NOT NULL, + "provider_instance_id" UUID, + "prompt_tokens" INTEGER NOT NULL DEFAULT 0, + "completion_tokens" INTEGER NOT NULL DEFAULT 0, + "total_tokens" INTEGER NOT NULL DEFAULT 0, + "cost_cents" DOUBLE PRECISION, + "task_type" VARCHAR(50), + "conversation_id" UUID, + "duration_ms" INTEGER, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "llm_usage_logs_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex: cron_schedules +CREATE INDEX "cron_schedules_workspace_id_idx" ON "cron_schedules"("workspace_id"); +CREATE INDEX "cron_schedules_workspace_id_enabled_idx" ON "cron_schedules"("workspace_id", "enabled"); +CREATE INDEX "cron_schedules_next_run_idx" ON "cron_schedules"("next_run"); + +-- CreateIndex: workspace_llm_settings +CREATE UNIQUE INDEX "workspace_llm_settings_workspace_id_key" ON "workspace_llm_settings"("workspace_id"); +CREATE INDEX "workspace_llm_settings_workspace_id_idx" ON "workspace_llm_settings"("workspace_id"); +CREATE INDEX "workspace_llm_settings_default_llm_provider_id_idx" ON "workspace_llm_settings"("default_llm_provider_id"); +CREATE INDEX "workspace_llm_settings_default_personality_id_idx" ON "workspace_llm_settings"("default_personality_id"); + +-- CreateIndex: quality_gates +CREATE UNIQUE INDEX "quality_gates_workspace_id_name_key" ON "quality_gates"("workspace_id", "name"); +CREATE INDEX "quality_gates_workspace_id_idx" ON "quality_gates"("workspace_id"); +CREATE INDEX "quality_gates_workspace_id_is_enabled_idx" ON "quality_gates"("workspace_id", "is_enabled"); + +-- CreateIndex: task_rejections +CREATE INDEX "task_rejections_task_id_idx" ON "task_rejections"("task_id"); +CREATE INDEX "task_rejections_workspace_id_idx" ON "task_rejections"("workspace_id"); +CREATE INDEX "task_rejections_agent_id_idx" ON "task_rejections"("agent_id"); +CREATE INDEX "task_rejections_escalated_idx" ON "task_rejections"("escalated"); +CREATE INDEX "task_rejections_manual_review_idx" ON "task_rejections"("manual_review"); + +-- CreateIndex: token_budgets +CREATE UNIQUE INDEX "token_budgets_task_id_key" ON "token_budgets"("task_id"); +CREATE INDEX "token_budgets_task_id_idx" ON "token_budgets"("task_id"); +CREATE INDEX "token_budgets_workspace_id_idx" ON "token_budgets"("workspace_id"); +CREATE INDEX "token_budgets_suspicious_pattern_idx" ON "token_budgets"("suspicious_pattern"); + +-- CreateIndex: llm_usage_logs +CREATE INDEX "llm_usage_logs_workspace_id_idx" ON "llm_usage_logs"("workspace_id"); +CREATE INDEX "llm_usage_logs_workspace_id_created_at_idx" ON "llm_usage_logs"("workspace_id", "created_at"); +CREATE INDEX "llm_usage_logs_user_id_idx" ON "llm_usage_logs"("user_id"); +CREATE INDEX "llm_usage_logs_provider_idx" ON "llm_usage_logs"("provider"); +CREATE INDEX "llm_usage_logs_model_idx" ON "llm_usage_logs"("model"); +CREATE INDEX "llm_usage_logs_provider_instance_id_idx" ON "llm_usage_logs"("provider_instance_id"); +CREATE INDEX "llm_usage_logs_task_type_idx" ON "llm_usage_logs"("task_type"); +CREATE INDEX "llm_usage_logs_conversation_id_idx" ON "llm_usage_logs"("conversation_id"); + +-- AddForeignKey: cron_schedules +ALTER TABLE "cron_schedules" ADD CONSTRAINT "cron_schedules_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey: workspace_llm_settings +ALTER TABLE "workspace_llm_settings" ADD CONSTRAINT "workspace_llm_settings_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "workspace_llm_settings" ADD CONSTRAINT "workspace_llm_settings_default_llm_provider_id_fkey" FOREIGN KEY ("default_llm_provider_id") REFERENCES "llm_provider_instances"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "workspace_llm_settings" ADD CONSTRAINT "workspace_llm_settings_default_personality_id_fkey" FOREIGN KEY ("default_personality_id") REFERENCES "personalities"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey: quality_gates +ALTER TABLE "quality_gates" ADD CONSTRAINT "quality_gates_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey: llm_usage_logs +ALTER TABLE "llm_usage_logs" ADD CONSTRAINT "llm_usage_logs_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "llm_usage_logs" ADD CONSTRAINT "llm_usage_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "llm_usage_logs" ADD CONSTRAINT "llm_usage_logs_provider_instance_id_fkey" FOREIGN KEY ("provider_instance_id") REFERENCES "llm_provider_instances"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index a706457..791fba5 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -66,14 +66,10 @@ async function bootstrap() { origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void ): void => { - // SECURITY: In production, reject requests with no Origin header. - // In development, allow no-origin requests (Postman, curl, mobile apps). + // Allow requests with no Origin header (health checks, server-to-server, + // load balancer probes). These are not cross-origin requests per the CORS spec. if (!origin) { - if (isDevelopment) { - callback(null, true); - } else { - callback(new Error("CORS: Origin header is required")); - } + callback(null, true); return; }