DevOS v1 + Phase 2: unified resource model, infra ops, website deployment, AI chat ops
- Monorepo: NestJS API (Fastify) + React web (antd/ProComponents) + shared packages - PostgreSQL/Prisma (unified resources, relations graph, audit, events, jobs) - Auth/RBAC/workspace isolation, credential vault (AES-256-GCM) - Servers/SSH, Docker discovery & lifecycle, Job Center - Websites & deployments (git provider credential clone, env, health verification) - Domains/DNS (Cloudflare adapter), certificates (TLS probe), monitoring - Automation (cron/event/webhook HMAC + notifications), AI Gateway, MCP (29 tools) - AI chat window (LLM function-calling + rules fallback, SSE streaming, approvals) - Notes: Vditor WYSIWYG markdown editor, non-blocking autosave - Tests: 35 unit/security tests; UI audit clean (desktop+mobile, deep links)
This commit is contained in:
@@ -0,0 +1,581 @@
|
||||
-- CreateSchema
|
||||
CREATE SCHEMA IF NOT EXISTS "public";
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ResourceType" AS ENUM ('SERVER', 'DOMAIN', 'WEBSITE', 'REPOSITORY', 'DATABASE', 'CONTAINER', 'EMAIL_ACCOUNT', 'PROJECT', 'TASK', 'NOTE', 'DOCUMENT', 'BACKUP', 'DEPLOYMENT', 'MONITOR', 'CREDENTIAL', 'AUTOMATION', 'AI_RUN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ResourceStatus" AS ENUM ('PENDING', 'ACTIVE', 'ERROR', 'OFFLINE', 'ARCHIVED', 'UNKNOWN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "WorkspaceRole" AS ENUM ('OWNER', 'ADMIN', 'MEMBER', 'VIEWER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RiskLevel" AS ENUM ('LOW', 'MEDIUM', 'HIGH', 'CRITICAL');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ActorType" AS ENUM ('USER', 'SYSTEM', 'AGENT', 'MCP');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "JobStatus" AS ENUM ('PENDING', 'RUNNING', 'SUCCEEDED', 'FAILED', 'CANCELLED', 'DEAD');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "DeploymentStatus" AS ENUM ('QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED', 'CANCELLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CredentialType" AS ENUM ('SSH_PASSWORD', 'SSH_KEY', 'API_TOKEN', 'DNS_PROVIDER', 'GIT_TOKEN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ServerAuthType" AS ENUM ('PASSWORD', 'SSH_KEY');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TriggerType" AS ENUM ('MANUAL', 'CRON', 'EVENT', 'WEBHOOK');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AiRunStatus" AS ENUM ('PLANNING', 'AWAITING_APPROVAL', 'RUNNING', 'SUCCEEDED', 'FAILED', 'REJECTED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"passwordHash" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "workspaces" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "workspaces_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "workspace_members" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"role" "WorkspaceRole" NOT NULL DEFAULT 'MEMBER',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "workspace_members_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "resources" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"type" "ResourceType" NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"provider" TEXT,
|
||||
"status" "ResourceStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "resources_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "resource_relations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"sourceId" TEXT NOT NULL,
|
||||
"targetId" TEXT NOT NULL,
|
||||
"relationType" TEXT NOT NULL,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "resource_relations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "credentials" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT,
|
||||
"name" TEXT NOT NULL,
|
||||
"type" "CredentialType" NOT NULL,
|
||||
"provider" TEXT,
|
||||
"encryptedData" TEXT NOT NULL,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"lastUsedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "credentials_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "audit_logs" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"actorType" "ActorType" NOT NULL,
|
||||
"actorId" TEXT,
|
||||
"action" TEXT NOT NULL,
|
||||
"resourceType" TEXT,
|
||||
"resourceId" TEXT,
|
||||
"riskLevel" "RiskLevel" NOT NULL DEFAULT 'LOW',
|
||||
"status" TEXT NOT NULL DEFAULT 'SUCCESS',
|
||||
"requestId" TEXT,
|
||||
"ip" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "events" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"resourceId" TEXT,
|
||||
"payload" JSONB NOT NULL DEFAULT '{}',
|
||||
"correlationId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "events_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "jobs" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"status" "JobStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"payload" JSONB NOT NULL DEFAULT '{}',
|
||||
"result" JSONB,
|
||||
"error" TEXT,
|
||||
"attempts" INTEGER NOT NULL DEFAULT 0,
|
||||
"maxAttempts" INTEGER NOT NULL DEFAULT 3,
|
||||
"runAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"startedAt" TIMESTAMP(3),
|
||||
"finishedAt" TIMESTAMP(3),
|
||||
"correlationId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "jobs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "servers" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"host" TEXT NOT NULL,
|
||||
"port" INTEGER NOT NULL DEFAULT 22,
|
||||
"username" TEXT NOT NULL,
|
||||
"authType" "ServerAuthType" NOT NULL DEFAULT 'SSH_KEY',
|
||||
"provider" TEXT NOT NULL DEFAULT 'ssh',
|
||||
"credentialId" TEXT,
|
||||
"osInfo" TEXT,
|
||||
"labels" JSONB NOT NULL DEFAULT '{}',
|
||||
"lastSeenAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "servers_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "containers" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"serverId" TEXT NOT NULL,
|
||||
"containerId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"image" TEXT NOT NULL,
|
||||
"state" TEXT NOT NULL,
|
||||
"ports" JSONB NOT NULL DEFAULT '{}',
|
||||
"labels" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "containers_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "websites" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"primaryDomain" TEXT,
|
||||
"serverId" TEXT,
|
||||
"repositoryUrl" TEXT,
|
||||
"deployType" TEXT NOT NULL DEFAULT 'DOCKER',
|
||||
"port" INTEGER NOT NULL DEFAULT 80,
|
||||
"healthPath" TEXT NOT NULL DEFAULT '/',
|
||||
"env" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "websites_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "domains" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL DEFAULT 'MANUAL',
|
||||
"credentialId" TEXT,
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "domains_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "domain_records" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"domainId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"ttl" INTEGER NOT NULL DEFAULT 300,
|
||||
"proxied" BOOLEAN NOT NULL DEFAULT false,
|
||||
"providerRecordId" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "domain_records_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "deployments" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"websiteId" TEXT NOT NULL,
|
||||
"status" "DeploymentStatus" NOT NULL DEFAULT 'QUEUED',
|
||||
"trigger" TEXT NOT NULL DEFAULT 'MANUAL',
|
||||
"progress" INTEGER NOT NULL DEFAULT 0,
|
||||
"error" TEXT,
|
||||
"commit" TEXT,
|
||||
"config" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"finishedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "deployments_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "deployment_logs" (
|
||||
"id" TEXT NOT NULL,
|
||||
"deploymentId" TEXT NOT NULL,
|
||||
"seq" INTEGER NOT NULL,
|
||||
"stream" TEXT NOT NULL DEFAULT 'stdout',
|
||||
"line" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "deployment_logs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "monitors" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"websiteId" TEXT,
|
||||
"serverId" TEXT,
|
||||
"name" TEXT NOT NULL,
|
||||
"kind" TEXT NOT NULL DEFAULT 'HTTP',
|
||||
"target" TEXT NOT NULL,
|
||||
"intervalSec" INTEGER NOT NULL DEFAULT 60,
|
||||
"timeoutMs" INTEGER NOT NULL DEFAULT 10000,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"lastStatus" TEXT NOT NULL DEFAULT 'UNKNOWN',
|
||||
"consecutiveFailures" INTEGER NOT NULL DEFAULT 0,
|
||||
"lastCheckedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "monitors_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "monitor_results" (
|
||||
"id" TEXT NOT NULL,
|
||||
"monitorId" TEXT NOT NULL,
|
||||
"ok" BOOLEAN NOT NULL,
|
||||
"status" TEXT,
|
||||
"latencyMs" INTEGER,
|
||||
"error" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "monitor_results_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "automations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"triggerType" "TriggerType" NOT NULL,
|
||||
"triggerConfig" JSONB NOT NULL DEFAULT '{}',
|
||||
"conditions" JSONB NOT NULL DEFAULT '[]',
|
||||
"actions" JSONB NOT NULL DEFAULT '[]',
|
||||
"lastTriggeredAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "automations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "automation_runs" (
|
||||
"id" TEXT NOT NULL,
|
||||
"automationId" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'RUNNING',
|
||||
"trigger" JSONB NOT NULL DEFAULT '{}',
|
||||
"stepIndex" INTEGER NOT NULL DEFAULT 0,
|
||||
"logs" JSONB NOT NULL DEFAULT '[]',
|
||||
"error" TEXT,
|
||||
"correlationId" TEXT,
|
||||
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"finishedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "automation_runs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ai_runs" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"objective" TEXT NOT NULL,
|
||||
"status" "AiRunStatus" NOT NULL DEFAULT 'PLANNING',
|
||||
"riskLevel" "RiskLevel" NOT NULL DEFAULT 'LOW',
|
||||
"plan" JSONB NOT NULL DEFAULT '[]',
|
||||
"toolCalls" JSONB NOT NULL DEFAULT '[]',
|
||||
"verification" JSONB,
|
||||
"result" JSONB,
|
||||
"error" TEXT,
|
||||
"approvedBy" TEXT,
|
||||
"approvedAt" TIMESTAMP(3),
|
||||
"correlationId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ai_runs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "workspaces_slug_key" ON "workspaces"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workspace_members_userId_idx" ON "workspace_members"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "workspace_members_workspaceId_userId_key" ON "workspace_members"("workspaceId", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "resources_workspaceId_idx" ON "resources"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "resources_type_status_idx" ON "resources"("type", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "resources_workspaceId_type_idx" ON "resources"("workspaceId", "type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "resource_relations_sourceId_idx" ON "resource_relations"("sourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "resource_relations_targetId_idx" ON "resource_relations"("targetId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "resource_relations_workspaceId_idx" ON "resource_relations"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "resource_relations_sourceId_targetId_relationType_key" ON "resource_relations"("sourceId", "targetId", "relationType");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "credentials_resourceId_key" ON "credentials"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "credentials_workspaceId_idx" ON "credentials"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "audit_logs_workspaceId_createdAt_idx" ON "audit_logs"("workspaceId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "audit_logs_resourceType_resourceId_idx" ON "audit_logs"("resourceType", "resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "events_eventType_createdAt_idx" ON "events"("eventType", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "events_workspaceId_createdAt_idx" ON "events"("workspaceId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "jobs_status_runAt_idx" ON "jobs"("status", "runAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "jobs_workspaceId_idx" ON "jobs"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "servers_resourceId_key" ON "servers"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "servers_workspaceId_idx" ON "servers"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "containers_resourceId_key" ON "containers"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "containers_serverId_idx" ON "containers"("serverId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "containers_workspaceId_idx" ON "containers"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "websites_resourceId_key" ON "websites"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "websites_workspaceId_idx" ON "websites"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "domains_resourceId_key" ON "domains"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "domains_workspaceId_idx" ON "domains"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "domain_records_workspaceId_idx" ON "domain_records"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "domain_records_domainId_type_name_value_key" ON "domain_records"("domainId", "type", "name", "value");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "deployments_resourceId_key" ON "deployments"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "deployments_websiteId_createdAt_idx" ON "deployments"("websiteId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "deployments_workspaceId_createdAt_idx" ON "deployments"("workspaceId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "deployment_logs_deploymentId_seq_idx" ON "deployment_logs"("deploymentId", "seq");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "monitors_resourceId_key" ON "monitors"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "monitors_enabled_lastCheckedAt_idx" ON "monitors"("enabled", "lastCheckedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "monitors_workspaceId_idx" ON "monitors"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "monitor_results_monitorId_createdAt_idx" ON "monitor_results"("monitorId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "automations_resourceId_key" ON "automations"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "automations_workspaceId_idx" ON "automations"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "automations_triggerType_enabled_idx" ON "automations"("triggerType", "enabled");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "automation_runs_automationId_startedAt_idx" ON "automation_runs"("automationId", "startedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ai_runs_resourceId_key" ON "ai_runs"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ai_runs_workspaceId_createdAt_idx" ON "ai_runs"("workspaceId", "createdAt");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "resources" ADD CONSTRAINT "resources_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "resource_relations" ADD CONSTRAINT "resource_relations_sourceId_fkey" FOREIGN KEY ("sourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "resource_relations" ADD CONSTRAINT "resource_relations_targetId_fkey" FOREIGN KEY ("targetId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "credentials" ADD CONSTRAINT "credentials_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "servers" ADD CONSTRAINT "servers_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "containers" ADD CONSTRAINT "containers_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "containers" ADD CONSTRAINT "containers_serverId_fkey" FOREIGN KEY ("serverId") REFERENCES "servers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "websites" ADD CONSTRAINT "websites_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "websites" ADD CONSTRAINT "websites_serverId_fkey" FOREIGN KEY ("serverId") REFERENCES "servers"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "domains" ADD CONSTRAINT "domains_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "domain_records" ADD CONSTRAINT "domain_records_domainId_fkey" FOREIGN KEY ("domainId") REFERENCES "domains"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "deployments" ADD CONSTRAINT "deployments_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "deployments" ADD CONSTRAINT "deployments_websiteId_fkey" FOREIGN KEY ("websiteId") REFERENCES "websites"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "deployment_logs" ADD CONSTRAINT "deployment_logs_deploymentId_fkey" FOREIGN KEY ("deploymentId") REFERENCES "deployments"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "monitors" ADD CONSTRAINT "monitors_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "monitors" ADD CONSTRAINT "monitors_websiteId_fkey" FOREIGN KEY ("websiteId") REFERENCES "websites"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "monitors" ADD CONSTRAINT "monitors_serverId_fkey" FOREIGN KEY ("serverId") REFERENCES "servers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "monitor_results" ADD CONSTRAINT "monitor_results_monitorId_fkey" FOREIGN KEY ("monitorId") REFERENCES "monitors"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "automations" ADD CONSTRAINT "automations_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "automation_runs" ADD CONSTRAINT "automation_runs_automationId_fkey" FOREIGN KEY ("automationId") REFERENCES "automations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ai_runs" ADD CONSTRAINT "ai_runs_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "deployments" ADD COLUMN "buildStrategy" TEXT,
|
||||
ADD COLUMN "deployStrategy" TEXT,
|
||||
ADD COLUMN "environment" TEXT,
|
||||
ADD COLUMN "verification" JSONB;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "websites" ADD COLUMN "environment" TEXT NOT NULL DEFAULT 'production',
|
||||
ADD COLUMN "repositoryId" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "GitConnection" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL,
|
||||
"apiBase" TEXT,
|
||||
"credentialId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "GitConnection_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Repository" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"connectionId" TEXT,
|
||||
"provider" TEXT NOT NULL DEFAULT 'GENERIC',
|
||||
"fullName" TEXT NOT NULL,
|
||||
"cloneUrl" TEXT NOT NULL,
|
||||
"defaultBranch" TEXT NOT NULL DEFAULT 'main',
|
||||
"visibility" TEXT NOT NULL DEFAULT 'private',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Repository_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Task" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL DEFAULT '',
|
||||
"status" TEXT NOT NULL DEFAULT 'TODO',
|
||||
"priority" TEXT NOT NULL DEFAULT 'MEDIUM',
|
||||
"labels" JSONB NOT NULL DEFAULT '[]',
|
||||
"dueAt" TIMESTAMP(3),
|
||||
"projectId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Task_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Note" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL DEFAULT '',
|
||||
"tags" JSONB NOT NULL DEFAULT '[]',
|
||||
"projectId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Note_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Notification" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT,
|
||||
"title" TEXT NOT NULL,
|
||||
"body" TEXT NOT NULL DEFAULT '',
|
||||
"level" TEXT NOT NULL DEFAULT 'INFO',
|
||||
"readAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Notification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Certificate" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"resourceId" TEXT NOT NULL,
|
||||
"domain" TEXT NOT NULL,
|
||||
"domainId" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'PENDING',
|
||||
"issuer" TEXT,
|
||||
"notAfter" TIMESTAMP(3),
|
||||
"lastCheckedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Certificate_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "GitConnection_resourceId_key" ON "GitConnection"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "GitConnection_workspaceId_idx" ON "GitConnection"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Repository_resourceId_key" ON "Repository"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Repository_workspaceId_idx" ON "Repository"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Repository_workspaceId_cloneUrl_key" ON "Repository"("workspaceId", "cloneUrl");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Task_resourceId_key" ON "Task"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Task_workspaceId_status_idx" ON "Task"("workspaceId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Task_projectId_idx" ON "Task"("projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Note_resourceId_key" ON "Note"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Note_workspaceId_idx" ON "Note"("workspaceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Notification_workspaceId_createdAt_idx" ON "Notification"("workspaceId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Certificate_resourceId_key" ON "Certificate"("resourceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Certificate_workspaceId_idx" ON "Certificate"("workspaceId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "websites" ADD CONSTRAINT "websites_repositoryId_fkey" FOREIGN KEY ("repositoryId") REFERENCES "Repository"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "GitConnection" ADD CONSTRAINT "GitConnection_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Repository" ADD CONSTRAINT "Repository_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Repository" ADD CONSTRAINT "Repository_connectionId_fkey" FOREIGN KEY ("connectionId") REFERENCES "GitConnection"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Task" ADD CONSTRAINT "Task_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Note" ADD CONSTRAINT "Note_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Certificate" ADD CONSTRAINT "Certificate_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "resources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- Phase B: 关系类型标准化(旧 v1 小写名 → Phase2 大写规范名)
|
||||
UPDATE "resource_relations" SET "relationType" = 'WEBSITE_DEPLOYED_TO_SERVER' WHERE "relationType" = 'deployed_on';
|
||||
UPDATE "resource_relations" SET "relationType" = 'SERVER_HAS_CONTAINER' WHERE "relationType" = 'runs_container';
|
||||
UPDATE "resource_relations" SET "relationType" = 'WEBSITE_USES_DOMAIN' WHERE "relationType" = 'resolves_to';
|
||||
UPDATE "resource_relations" SET "relationType" = 'WEBSITE_HAS_DEPLOYMENT' WHERE "relationType" = 'has_deployment';
|
||||
UPDATE "resource_relations" SET "relationType" = 'WEBSITE_HAS_MONITOR' WHERE "relationType" = 'monitors';
|
||||
@@ -0,0 +1,3 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "ResourceType" ADD VALUE 'CERTIFICATE';
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Note" ADD COLUMN "folder" TEXT,
|
||||
ADD COLUMN "pinned" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "ai_runs" ADD COLUMN "source" TEXT NOT NULL DEFAULT 'UI';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
provider = "postgresql"
|
||||
@@ -0,0 +1,734 @@
|
||||
// DevOS v1 — PostgreSQL + Prisma schema
|
||||
// 设计原则(04-DATABASE-SCHEMA.md):
|
||||
// - resources 表承担统一身份/发现/图谱公共字段;
|
||||
// - 具备大量运行状态的资源使用类型化领域表(servers/websites/...),通过 resourceId 与 resources 一对一关联;
|
||||
// - 资源关系独立成表(resource_relations),图谱可演进而非堆在一张大表;
|
||||
// - 凭据密文独立存储,绝不与普通元数据混放。
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ============ 枚举 ============
|
||||
|
||||
enum ResourceType {
|
||||
SERVER
|
||||
DOMAIN
|
||||
WEBSITE
|
||||
REPOSITORY
|
||||
CERTIFICATE
|
||||
DATABASE
|
||||
CONTAINER
|
||||
EMAIL_ACCOUNT
|
||||
PROJECT
|
||||
TASK
|
||||
NOTE
|
||||
DOCUMENT
|
||||
BACKUP
|
||||
DEPLOYMENT
|
||||
MONITOR
|
||||
CREDENTIAL
|
||||
AUTOMATION
|
||||
AI_RUN
|
||||
}
|
||||
|
||||
enum ResourceStatus {
|
||||
PENDING
|
||||
ACTIVE
|
||||
ERROR
|
||||
OFFLINE
|
||||
ARCHIVED
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
enum WorkspaceRole {
|
||||
OWNER
|
||||
ADMIN
|
||||
MEMBER
|
||||
VIEWER
|
||||
}
|
||||
|
||||
enum RiskLevel {
|
||||
LOW
|
||||
MEDIUM
|
||||
HIGH
|
||||
CRITICAL
|
||||
}
|
||||
|
||||
enum ActorType {
|
||||
USER
|
||||
SYSTEM
|
||||
AGENT
|
||||
MCP
|
||||
}
|
||||
|
||||
enum JobStatus {
|
||||
PENDING
|
||||
RUNNING
|
||||
SUCCEEDED
|
||||
FAILED
|
||||
CANCELLED
|
||||
DEAD
|
||||
}
|
||||
|
||||
enum DeploymentStatus {
|
||||
QUEUED
|
||||
RUNNING
|
||||
SUCCEEDED
|
||||
FAILED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum CredentialType {
|
||||
SSH_PASSWORD
|
||||
SSH_KEY
|
||||
API_TOKEN
|
||||
DNS_PROVIDER
|
||||
GIT_TOKEN
|
||||
}
|
||||
|
||||
enum ServerAuthType {
|
||||
PASSWORD
|
||||
SSH_KEY
|
||||
}
|
||||
|
||||
enum TriggerType {
|
||||
MANUAL
|
||||
CRON
|
||||
EVENT
|
||||
WEBHOOK
|
||||
}
|
||||
|
||||
enum AiRunStatus {
|
||||
PLANNING
|
||||
AWAITING_APPROVAL
|
||||
RUNNING
|
||||
SUCCEEDED
|
||||
FAILED
|
||||
REJECTED
|
||||
}
|
||||
|
||||
// ============ 身份与工作区 ============
|
||||
|
||||
/// # 数据库表 users:平台用户
|
||||
model User {
|
||||
/// # 数据库字段 邮箱(登录名,唯一)
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
/// # 数据库字段 密码哈希(argon2id/scrypt,绝不明文)
|
||||
passwordHash String
|
||||
name String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
memberships WorkspaceMember[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
/// # 数据库表 workspaces:租户/安全边界
|
||||
model Workspace {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
slug String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
members WorkspaceMember[]
|
||||
resources Resource[]
|
||||
|
||||
@@map("workspaces")
|
||||
}
|
||||
|
||||
/// # 数据库表 workspace_members:用户-工作区关系与角色
|
||||
model WorkspaceMember {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
userId String
|
||||
role WorkspaceRole @default(MEMBER)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([workspaceId, userId])
|
||||
@@index([userId])
|
||||
@@map("workspace_members")
|
||||
}
|
||||
|
||||
// ============ 统一资源模型 ============
|
||||
|
||||
/// # 数据库表 resources:统一资源身份/发现/图谱公共字段
|
||||
model Resource {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
type ResourceType
|
||||
name String
|
||||
provider String?
|
||||
status ResourceStatus @default(PENDING)
|
||||
/// # 数据库字段 扩展元数据(敏感信息禁止写入)
|
||||
metadata Json @default("{}")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
outgoingRelations ResourceRelation[] @relation("source")
|
||||
incomingRelations ResourceRelation[] @relation("target")
|
||||
credential Credential?
|
||||
server Server?
|
||||
container Container?
|
||||
website Website?
|
||||
domain Domain?
|
||||
deployment Deployment?
|
||||
monitor Monitor?
|
||||
automation Automation?
|
||||
aiRun AiRun?
|
||||
gitConnection GitConnection?
|
||||
repository Repository?
|
||||
task Task?
|
||||
note Note?
|
||||
certificate Certificate?
|
||||
|
||||
@@index([workspaceId])
|
||||
@@index([type, status])
|
||||
@@index([workspaceId, type])
|
||||
@@map("resources")
|
||||
}
|
||||
|
||||
/// # 数据库表 resource_relations:资源关系图(有向边)
|
||||
model ResourceRelation {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
sourceId String
|
||||
targetId String
|
||||
/// # 数据库字段 关系类型,如 deployed_on / has_domain / resolves_to / monitors
|
||||
relationType String
|
||||
metadata Json @default("{}")
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
source Resource @relation("source", fields: [sourceId], references: [id], onDelete: Cascade)
|
||||
target Resource @relation("target", fields: [targetId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([sourceId, targetId, relationType])
|
||||
@@index([sourceId])
|
||||
@@index([targetId])
|
||||
@@index([workspaceId])
|
||||
@@map("resource_relations")
|
||||
}
|
||||
|
||||
// ============ 凭据保险库 ============
|
||||
|
||||
/// # 数据库表 credentials:加密凭据(信封加密后的密文,禁止记录明文)
|
||||
model Credential {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String? @unique
|
||||
name String
|
||||
type CredentialType
|
||||
provider String?
|
||||
/// # 数据库字段 AES-256-GCM 密文 JSON(iv/tag/ciphertext,base64)
|
||||
encryptedData String
|
||||
metadata Json @default("{}")
|
||||
lastUsedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource? @relation(fields: [resourceId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([workspaceId])
|
||||
@@map("credentials")
|
||||
}
|
||||
|
||||
// ============ 审计 / 事件 / 任务 ============
|
||||
|
||||
/// # 数据库表 audit_logs:审计日志(每个变更必写)
|
||||
model AuditLog {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
actorType ActorType
|
||||
actorId String?
|
||||
action String
|
||||
resourceType String?
|
||||
resourceId String?
|
||||
riskLevel RiskLevel @default(LOW)
|
||||
status String @default("SUCCESS")
|
||||
requestId String?
|
||||
ip String?
|
||||
metadata Json @default("{}")
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([workspaceId, createdAt])
|
||||
@@index([resourceType, resourceId])
|
||||
@@map("audit_logs")
|
||||
}
|
||||
|
||||
/// # 数据库表 events:领域事件(持久化 + 分发)
|
||||
model EventRecord {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
eventType String
|
||||
resourceId String?
|
||||
payload Json @default("{}")
|
||||
correlationId String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([eventType, createdAt])
|
||||
@@index([workspaceId, createdAt])
|
||||
@@map("events")
|
||||
}
|
||||
|
||||
/// # 数据库表 jobs:异步任务(持久化状态机 + Redis 派发)
|
||||
model Job {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
type String
|
||||
status JobStatus @default(PENDING)
|
||||
payload Json @default("{}")
|
||||
result Json?
|
||||
error String?
|
||||
attempts Int @default(0)
|
||||
maxAttempts Int @default(3)
|
||||
runAt DateTime @default(now())
|
||||
startedAt DateTime?
|
||||
finishedAt DateTime?
|
||||
correlationId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([status, runAt])
|
||||
@@index([workspaceId])
|
||||
@@map("jobs")
|
||||
}
|
||||
|
||||
// ============ 基础设施领域表 ============
|
||||
|
||||
/// # 数据库表 servers:受管计算目标(SSH)
|
||||
model Server {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
name String
|
||||
host String
|
||||
port Int @default(22)
|
||||
username String
|
||||
authType ServerAuthType @default(SSH_KEY)
|
||||
provider String @default("ssh")
|
||||
credentialId String?
|
||||
osInfo String?
|
||||
labels Json @default("{}")
|
||||
lastSeenAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
containers Container[]
|
||||
websites Website[]
|
||||
monitors Monitor[]
|
||||
|
||||
@@index([workspaceId])
|
||||
@@map("servers")
|
||||
}
|
||||
|
||||
/// # 数据库表 containers:Docker 容器发现结果
|
||||
model Container {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
serverId String
|
||||
containerId String
|
||||
name String
|
||||
image String
|
||||
state String
|
||||
ports Json @default("{}")
|
||||
labels Json @default("{}")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
server Server @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([serverId])
|
||||
@@index([workspaceId])
|
||||
@@map("containers")
|
||||
}
|
||||
|
||||
/// # 数据库表 websites:可部署应用资源
|
||||
model Website {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
name String
|
||||
primaryDomain String?
|
||||
serverId String?
|
||||
repositoryUrl String?
|
||||
/// # 数据库字段 关联仓库(Git Provider 凭据克隆;优先于 repositoryUrl)
|
||||
repositoryId String?
|
||||
/// # 数据库字段 部署环境:development / staging / production
|
||||
environment String @default("production")
|
||||
/// # 数据库字段 部署类型:DOCKER / STATIC / NODE
|
||||
deployType String @default("DOCKER")
|
||||
/// # 数据库字段 容器对外端口
|
||||
port Int @default(80)
|
||||
healthPath String @default("/")
|
||||
env Json @default("{}")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
server Server? @relation(fields: [serverId], references: [id], onDelete: SetNull)
|
||||
deployments Deployment[]
|
||||
monitors Monitor[]
|
||||
repository Repository? @relation(fields: [repositoryId], references: [id])
|
||||
|
||||
@@index([workspaceId])
|
||||
@@map("websites")
|
||||
}
|
||||
|
||||
/// # 数据库表 domains:域名与 DNS 提供商绑定
|
||||
model Domain {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
name String
|
||||
provider String @default("MANUAL")
|
||||
credentialId String?
|
||||
expiresAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
records DomainRecord[]
|
||||
|
||||
@@index([workspaceId])
|
||||
@@map("domains")
|
||||
}
|
||||
|
||||
/// # 数据库表 domain_records:域名记录(手动 provider 时为本地事实源)
|
||||
model DomainRecord {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
domainId String
|
||||
type String
|
||||
name String
|
||||
value String
|
||||
ttl Int @default(300)
|
||||
proxied Boolean @default(false)
|
||||
providerRecordId String?
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
domain Domain @relation(fields: [domainId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([domainId, type, name, value])
|
||||
@@index([workspaceId])
|
||||
@@map("domain_records")
|
||||
}
|
||||
|
||||
/// # 数据库表 deployments:部署执行记录
|
||||
model Deployment {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
websiteId String
|
||||
status DeploymentStatus @default(QUEUED)
|
||||
trigger String @default("MANUAL")
|
||||
progress Int @default(0)
|
||||
/// # 数据库字段 失败原因(截断存储,不含敏感信息)
|
||||
error String?
|
||||
commit String?
|
||||
/// # 数据库字段 部署环境:development / staging / production
|
||||
environment String?
|
||||
/// # 数据库字段 构建策略:DOCKERFILE / STATIC_COPY
|
||||
buildStrategy String?
|
||||
/// # 数据库字段 部署策略:DOCKER_CONTAINER / STATIC_DIR
|
||||
deployStrategy String?
|
||||
/// # 数据库字段 健康验证证据 {ok,status,latencyMs,checkedAt}
|
||||
verification Json?
|
||||
/// # 数据库字段 部署参数快照(分支、镜像 tag 等)
|
||||
config Json @default("{}")
|
||||
createdAt DateTime @default(now())
|
||||
finishedAt DateTime?
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
website Website @relation(fields: [websiteId], references: [id], onDelete: Cascade)
|
||||
logs DeploymentLog[]
|
||||
|
||||
@@index([websiteId, createdAt])
|
||||
@@index([workspaceId, createdAt])
|
||||
@@map("deployments")
|
||||
}
|
||||
|
||||
/// # 数据库表 deployment_logs:部署日志行(顺序)
|
||||
model DeploymentLog {
|
||||
id String @id @default(uuid())
|
||||
deploymentId String
|
||||
seq Int
|
||||
stream String @default("stdout")
|
||||
line String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
deployment Deployment @relation(fields: [deploymentId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([deploymentId, seq])
|
||||
@@map("deployment_logs")
|
||||
}
|
||||
|
||||
/// # 数据库表 monitors:健康检查配置
|
||||
model Monitor {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
websiteId String?
|
||||
serverId String?
|
||||
name String
|
||||
/// # 数据库字段 检查类型:HTTP / TCP
|
||||
kind String @default("HTTP")
|
||||
target String
|
||||
intervalSec Int @default(60)
|
||||
timeoutMs Int @default(10000)
|
||||
enabled Boolean @default(true)
|
||||
lastStatus String @default("UNKNOWN")
|
||||
consecutiveFailures Int @default(0)
|
||||
lastCheckedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
website Website? @relation(fields: [websiteId], references: [id], onDelete: Cascade)
|
||||
server Server? @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
||||
results MonitorResult[]
|
||||
|
||||
@@index([enabled, lastCheckedAt])
|
||||
@@index([workspaceId])
|
||||
@@map("monitors")
|
||||
}
|
||||
|
||||
/// # 数据库表 monitor_results:监控结果历史
|
||||
model MonitorResult {
|
||||
id String @id @default(uuid())
|
||||
monitorId String
|
||||
ok Boolean
|
||||
status String?
|
||||
latencyMs Int?
|
||||
error String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
monitor Monitor @relation(fields: [monitorId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([monitorId, createdAt])
|
||||
@@map("monitor_results")
|
||||
}
|
||||
|
||||
// ============ 自动化 ============
|
||||
|
||||
/// # 数据库表 automations:Trigger → Condition → Action 工作流
|
||||
model Automation {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
name String
|
||||
enabled Boolean @default(false)
|
||||
triggerType TriggerType
|
||||
/// # 数据库字段 触发器配置(cron 表达式 / 事件名 / webhook token)
|
||||
triggerConfig Json @default("{}")
|
||||
/// # 数据库字段 条件列表 [{path,op,value}]
|
||||
conditions Json @default("[]")
|
||||
/// # 数据库字段 动作列表 [{type,args,timeoutMs,retries}]
|
||||
actions Json @default("[]")
|
||||
lastTriggeredAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
runs AutomationRun[]
|
||||
|
||||
@@index([workspaceId])
|
||||
@@index([triggerType, enabled])
|
||||
@@map("automations")
|
||||
}
|
||||
|
||||
/// # 数据库表 automation_runs:自动化执行历史
|
||||
model AutomationRun {
|
||||
id String @id @default(uuid())
|
||||
automationId String
|
||||
workspaceId String
|
||||
status String @default("RUNNING")
|
||||
trigger Json @default("{}")
|
||||
stepIndex Int @default(0)
|
||||
logs Json @default("[]")
|
||||
error String?
|
||||
correlationId String?
|
||||
startedAt DateTime @default(now())
|
||||
finishedAt DateTime?
|
||||
|
||||
automation Automation @relation(fields: [automationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([automationId, startedAt])
|
||||
@@map("automation_runs")
|
||||
}
|
||||
|
||||
// ============ AI Gateway ============
|
||||
|
||||
/// # 数据库表 ai_runs:AI 运行(计划→风控→审批→执行→验证)
|
||||
model AiRun {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
objective String
|
||||
status AiRunStatus @default(PLANNING)
|
||||
/// # 数据库字段 来源:UI / CHAT / MCP / AUTOMATION
|
||||
source String @default("UI")
|
||||
riskLevel RiskLevel @default(LOW)
|
||||
/// # 数据库字段 计划步骤 [{tool,args,reason,risk}]
|
||||
plan Json @default("[]")
|
||||
/// # 数据库字段 执行历史 [{tool,args,result,ok}]
|
||||
toolCalls Json @default("[]")
|
||||
/// # 数据库字段 验证结果 {ok, checks[]}
|
||||
verification Json?
|
||||
result Json?
|
||||
error String?
|
||||
approvedBy String?
|
||||
approvedAt DateTime?
|
||||
correlationId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([workspaceId, createdAt])
|
||||
@@map("ai_runs")
|
||||
}
|
||||
|
||||
// ============ Phase 2:Git / 生产力 / 通知 / 证书 ============
|
||||
|
||||
/// # 数据库表 git_connections:Git Provider 连接(GitHub/GitLab/Gitea + 凭据引用)
|
||||
model GitConnection {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
name String
|
||||
/// # 数据库字段 提供商:GITHUB / GITLAB / GITEA
|
||||
provider String
|
||||
/// # 数据库字段 自建实例 API 基址(Gitea/GitLab 自托管时填写)
|
||||
apiBase String?
|
||||
credentialId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
repositories Repository[]
|
||||
|
||||
@@index([workspaceId])
|
||||
}
|
||||
|
||||
/// # 数据库表 repositories:外部代码仓库聚合(不实现 Git 托管)
|
||||
model Repository {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
connectionId String?
|
||||
provider String @default("GENERIC")
|
||||
/// # 数据库字段 全名 owner/repo
|
||||
fullName String
|
||||
/// # 数据库字段 HTTPS 克隆地址(不含凭据)
|
||||
cloneUrl String
|
||||
defaultBranch String @default("main")
|
||||
/// # 数据库字段 private 标记
|
||||
visibility String @default("private")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
connection GitConnection? @relation(fields: [connectionId], references: [id], onDelete: SetNull)
|
||||
websites Website[]
|
||||
|
||||
@@unique([workspaceId, cloneUrl])
|
||||
@@index([workspaceId])
|
||||
}
|
||||
|
||||
/// # 数据库表 tasks:工作项(backlog/todo/doing/review/done)
|
||||
model Task {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
title String
|
||||
description String @default("")
|
||||
/// # 数据库字段 状态:BACKLOG / TODO / DOING / REVIEW / DONE
|
||||
status String @default("TODO")
|
||||
/// # 数据库字段 优先级:LOW / MEDIUM / HIGH / URGENT
|
||||
priority String @default("MEDIUM")
|
||||
labels Json @default("[]")
|
||||
dueAt DateTime?
|
||||
/// # 数据库字段 关联项目资源 ID(可空)
|
||||
projectId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([workspaceId, status])
|
||||
@@index([projectId])
|
||||
}
|
||||
|
||||
/// # 数据库表 notes:笔记/知识(markdown 存储)
|
||||
model Note {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
title String
|
||||
/// # 数据库字段 正文(HTML,TipTap 富文本)
|
||||
content String @default("")
|
||||
tags Json @default("[]")
|
||||
projectId String?
|
||||
/// # 数据库字段 置顶
|
||||
pinned Boolean @default(false)
|
||||
/// # 数据库字段 文件夹(分组)
|
||||
folder String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([workspaceId])
|
||||
}
|
||||
|
||||
/// # 数据库表 notifications:站内通知(自动化/事件投递)
|
||||
model Notification {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String?
|
||||
title String
|
||||
body String @default("")
|
||||
/// # 数据库字段 级别:INFO / WARN / ERROR
|
||||
level String @default("INFO")
|
||||
readAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([workspaceId, createdAt])
|
||||
}
|
||||
|
||||
/// # 数据库表 certificates:TLS 证书统一模型(探针检测)
|
||||
model Certificate {
|
||||
id String @id @default(uuid())
|
||||
workspaceId String
|
||||
resourceId String @unique
|
||||
/// # 数据库字段 证书主域名
|
||||
domain String
|
||||
domainId String?
|
||||
/// # 数据库字段 状态:PENDING / ACTIVE / EXPIRED / ERROR
|
||||
status String @default("PENDING")
|
||||
issuer String?
|
||||
notAfter DateTime?
|
||||
lastCheckedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([workspaceId])
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* # 种子脚本
|
||||
* 策略:幂等。按 .env 中 SEED_ADMIN_EMAIL/SEED_ADMIN_PASSWORD 创建管理员用户、
|
||||
* 默认工作区与 OWNER 成员关系;重复执行不会产生重复数据。
|
||||
*/
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { hashPassword } from "../packages/auth/src/index";
|
||||
|
||||
/** # 全局 Prisma 客户端(种子专用) **/
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/** # 入口:创建管理员与默认工作区 */
|
||||
async function main() {
|
||||
const email = process.env.SEED_ADMIN_EMAIL ?? "admin@devos.local";
|
||||
const password = process.env.SEED_ADMIN_PASSWORD ?? "Devos12345!";
|
||||
|
||||
let user = await prisma.user.findUnique({ where: { email } });
|
||||
if (!user) {
|
||||
user = await prisma.user.create({
|
||||
data: {
|
||||
email,
|
||||
name: "Admin",
|
||||
passwordHash: await hashPassword(password),
|
||||
},
|
||||
});
|
||||
console.log(`[seed] created admin user ${email}`);
|
||||
} else {
|
||||
console.log(`[seed] admin user ${email} already exists`);
|
||||
}
|
||||
|
||||
const slug = "main";
|
||||
let workspace = await prisma.workspace.findUnique({ where: { slug } });
|
||||
if (!workspace) {
|
||||
workspace = await prisma.workspace.create({
|
||||
data: { name: "Main Workspace", slug },
|
||||
});
|
||||
console.log(`[seed] created workspace ${workspace.name}`);
|
||||
}
|
||||
|
||||
const membership = await prisma.workspaceMember.findUnique({
|
||||
where: { workspaceId_userId: { workspaceId: workspace.id, userId: user.id } },
|
||||
});
|
||||
if (!membership) {
|
||||
await prisma.workspaceMember.create({
|
||||
data: { workspaceId: workspace.id, userId: user.id, role: "OWNER" },
|
||||
});
|
||||
console.log(`[seed] added admin as OWNER of workspace`);
|
||||
}
|
||||
console.log(`[seed] done. workspaceId=${workspace.id}`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(() => prisma.$disconnect());
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* # 管理员密码轮换脚本
|
||||
* 用法:SEED_ADMIN_EMAIL=... SEED_ADMIN_PASSWORD=... bun run prisma/set-admin-password.ts
|
||||
*/
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { hashPassword } from "../packages/auth/src/index";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const email = process.env.SEED_ADMIN_EMAIL;
|
||||
const password = process.env.SEED_ADMIN_PASSWORD;
|
||||
if (!email || !password || password.length < 5) {
|
||||
console.error("set SEED_ADMIN_EMAIL and SEED_ADMIN_PASSWORD (>=5 chars)");
|
||||
process.exit(1);
|
||||
}
|
||||
if (password.length < 12) {
|
||||
console.warn("[warn] weak password in use — 仅建议开发/内网环境");
|
||||
}
|
||||
// 按邮箱或用户名查找现有管理员
|
||||
const user =
|
||||
(await prisma.user.findFirst({ where: { email }, orderBy: { createdAt: "asc" } })) ??
|
||||
(await prisma.user.findFirst({ where: { email: { endsWith: "@devos.local" } }, orderBy: { createdAt: "asc" } }));
|
||||
if (!user) {
|
||||
console.error(`admin user not found (searched email=${email} and *@devos.local)`);
|
||||
process.exit(1);
|
||||
}
|
||||
const updated = await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { email, passwordHash: await hashPassword(password) },
|
||||
});
|
||||
console.log(`[fixed] account=${updated.email} (was ${user.email}) at ${new Date().toISOString()}`);
|
||||
}
|
||||
|
||||
main().finally(() => prisma.$disconnect());
|
||||
Reference in New Issue
Block a user