@allbluecn/database 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/migrations/20260804174501_init/migration.sql +1764 -0
- package/migrations/20260805174545_add_story_activity_is_ai/migration.sql +2 -0
- package/migrations/20260806035202_story_archived/migration.sql +6 -0
- package/migrations/20260806061157_builtin_independent_project/migration.sql +26 -0
- package/migrations/20260806144250_tanstack_ai_persistence/migration.sql +129 -0
- package/migrations/20260809080004_add_monitoring_observability/migration.sql +75 -0
- package/migrations/20260811112502_add_workspace/migration.sql +89 -0
- package/migrations/20260811153719_add_hosted_ai_tables/migration.sql +119 -0
- package/migrations/20260812043944_add_billing_tables/migration.sql +86 -0
- package/migrations/20260813040200_sub3_stripe_subscription_unique/migration.sql +6 -0
- package/migrations/20260813061345_add_enterprise_license_sso_audit_permissions/migration.sql +169 -0
- package/migrations/20260813132026_add_user_locale/migration.sql +2 -0
- package/migrations/20260814000000_add_story_composite_index/migration.sql +2 -0
- package/migrations/20260814161622_add_user_default_workspace_id/migration.sql +2 -0
- package/migrations/20260815025746_p2_seat_addon/migration.sql +15 -0
- package/migrations/20260815120000_creem_billing_neutralization/migration.sql +122 -0
- package/migrations/20260816212027_compliance_content/migration.sql +3 -0
- package/migrations/20260817204928_p6_user_agents_skills/migration.sql +57 -0
- package/migrations/20260818094327_user_level_subscription/migration.sql +43 -0
- package/migrations/20260819014924_team_membership_invitation/migration.sql +65 -0
- package/migrations/migration_lock.toml +3 -0
- package/package.json +8 -3
- package/prisma.config.ts +18 -0
- package/schema.prisma +2014 -0
- package/schema.sqlite.prisma +1584 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- 1. 为每个有 projectId IS NULL 的 Story 的 createdById 用户补建内置项目
|
|
2
|
+
DO $$
|
|
3
|
+
DECLARE uid text; pid text;
|
|
4
|
+
BEGIN
|
|
5
|
+
FOR uid IN SELECT DISTINCT "createdById" FROM "stories" WHERE "projectId" IS NULL LOOP
|
|
6
|
+
INSERT INTO "projects" ("id", "displayId", "name", "icon", "ownerId", "createdAt", "updatedAt")
|
|
7
|
+
VALUES (lower(gen_random_uuid()::text), 'STORY', '独立需求', 'sprout', uid, now(), now())
|
|
8
|
+
ON CONFLICT ("ownerId", "displayId") DO NOTHING;
|
|
9
|
+
|
|
10
|
+
SELECT "id" INTO pid FROM "projects" WHERE "ownerId" = uid AND "displayId" = 'STORY';
|
|
11
|
+
|
|
12
|
+
INSERT INTO "project_members" ("id", "projectId", "userId", "role", "status", "joinedAt")
|
|
13
|
+
VALUES (lower(gen_random_uuid()::text), pid, uid, 'owner', 'active', now())
|
|
14
|
+
ON CONFLICT ("projectId", "userId") DO NOTHING;
|
|
15
|
+
|
|
16
|
+
UPDATE "stories" SET "projectId" = pid WHERE "projectId" IS NULL AND "createdById" = uid;
|
|
17
|
+
END LOOP;
|
|
18
|
+
END $$;
|
|
19
|
+
|
|
20
|
+
-- 2. 添加 NOT NULL 约束
|
|
21
|
+
ALTER TABLE "stories" ALTER COLUMN "projectId" SET NOT NULL;
|
|
22
|
+
|
|
23
|
+
-- 3. 改外键 onDelete: SET NULL → RESTRICT
|
|
24
|
+
ALTER TABLE "stories" DROP CONSTRAINT "stories_projectId_fkey";
|
|
25
|
+
ALTER TABLE "stories" ADD CONSTRAINT "stories_projectId_fkey"
|
|
26
|
+
FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
-- DropForeignKey
|
|
2
|
+
ALTER TABLE "conversations" DROP CONSTRAINT "conversations_userId_fkey";
|
|
3
|
+
|
|
4
|
+
-- DropForeignKey
|
|
5
|
+
ALTER TABLE "messages" DROP CONSTRAINT "messages_conversationId_fkey";
|
|
6
|
+
|
|
7
|
+
-- DropTable
|
|
8
|
+
DROP TABLE "conversations";
|
|
9
|
+
|
|
10
|
+
-- DropTable
|
|
11
|
+
DROP TABLE "messages";
|
|
12
|
+
|
|
13
|
+
-- CreateTable
|
|
14
|
+
CREATE TABLE "chat_threads" (
|
|
15
|
+
"id" TEXT NOT NULL,
|
|
16
|
+
"userId" TEXT NOT NULL,
|
|
17
|
+
"title" TEXT,
|
|
18
|
+
"isArchived" BOOLEAN NOT NULL DEFAULT false,
|
|
19
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
20
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
21
|
+
|
|
22
|
+
CONSTRAINT "chat_threads_pkey" PRIMARY KEY ("id")
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
-- CreateTable
|
|
26
|
+
CREATE TABLE "chat_messages" (
|
|
27
|
+
"id" TEXT NOT NULL,
|
|
28
|
+
"threadId" TEXT NOT NULL,
|
|
29
|
+
"role" TEXT NOT NULL,
|
|
30
|
+
"content" TEXT NOT NULL,
|
|
31
|
+
"parts" JSONB,
|
|
32
|
+
"thinking" TEXT,
|
|
33
|
+
"citations" JSONB,
|
|
34
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
35
|
+
|
|
36
|
+
CONSTRAINT "chat_messages_pkey" PRIMARY KEY ("id")
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
-- CreateTable
|
|
40
|
+
CREATE TABLE "chat_runs" (
|
|
41
|
+
"id" TEXT NOT NULL,
|
|
42
|
+
"threadId" TEXT NOT NULL,
|
|
43
|
+
"userId" TEXT NOT NULL,
|
|
44
|
+
"status" TEXT NOT NULL,
|
|
45
|
+
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
46
|
+
"finishedAt" TIMESTAMP(3),
|
|
47
|
+
"detachedSince" TIMESTAMP(3),
|
|
48
|
+
"usage" JSONB,
|
|
49
|
+
"error" TEXT,
|
|
50
|
+
|
|
51
|
+
CONSTRAINT "chat_runs_pkey" PRIMARY KEY ("id")
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
-- CreateTable
|
|
55
|
+
CREATE TABLE "chat_interrupts" (
|
|
56
|
+
"id" TEXT NOT NULL,
|
|
57
|
+
"runId" TEXT NOT NULL,
|
|
58
|
+
"threadId" TEXT NOT NULL,
|
|
59
|
+
"userId" TEXT NOT NULL,
|
|
60
|
+
"kind" TEXT NOT NULL,
|
|
61
|
+
"toolName" TEXT,
|
|
62
|
+
"state" TEXT NOT NULL,
|
|
63
|
+
"payload" JSONB,
|
|
64
|
+
"resolution" JSONB,
|
|
65
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
66
|
+
"resolvedAt" TIMESTAMP(3),
|
|
67
|
+
|
|
68
|
+
CONSTRAINT "chat_interrupts_pkey" PRIMARY KEY ("id")
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
-- CreateTable
|
|
72
|
+
CREATE TABLE "generation_runs" (
|
|
73
|
+
"id" TEXT NOT NULL,
|
|
74
|
+
"threadId" TEXT NOT NULL,
|
|
75
|
+
"userId" TEXT NOT NULL,
|
|
76
|
+
"kind" TEXT NOT NULL,
|
|
77
|
+
"status" TEXT NOT NULL,
|
|
78
|
+
"input" JSONB,
|
|
79
|
+
"result" JSONB,
|
|
80
|
+
"artifactId" TEXT,
|
|
81
|
+
"error" TEXT,
|
|
82
|
+
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
83
|
+
"finishedAt" TIMESTAMP(3),
|
|
84
|
+
|
|
85
|
+
CONSTRAINT "generation_runs_pkey" PRIMARY KEY ("id")
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
-- CreateIndex
|
|
89
|
+
CREATE INDEX "chat_threads_userId_updatedAt_idx" ON "chat_threads"("userId", "updatedAt");
|
|
90
|
+
|
|
91
|
+
-- CreateIndex
|
|
92
|
+
CREATE INDEX "chat_messages_threadId_createdAt_idx" ON "chat_messages"("threadId", "createdAt");
|
|
93
|
+
|
|
94
|
+
-- CreateIndex
|
|
95
|
+
CREATE INDEX "chat_runs_threadId_startedAt_idx" ON "chat_runs"("threadId", "startedAt");
|
|
96
|
+
|
|
97
|
+
-- CreateIndex
|
|
98
|
+
CREATE INDEX "chat_runs_userId_status_idx" ON "chat_runs"("userId", "status");
|
|
99
|
+
|
|
100
|
+
-- CreateIndex
|
|
101
|
+
CREATE INDEX "chat_interrupts_runId_idx" ON "chat_interrupts"("runId");
|
|
102
|
+
|
|
103
|
+
-- CreateIndex
|
|
104
|
+
CREATE INDEX "chat_interrupts_threadId_state_idx" ON "chat_interrupts"("threadId", "state");
|
|
105
|
+
|
|
106
|
+
-- CreateIndex
|
|
107
|
+
CREATE INDEX "generation_runs_threadId_startedAt_idx" ON "generation_runs"("threadId", "startedAt");
|
|
108
|
+
|
|
109
|
+
-- CreateIndex
|
|
110
|
+
CREATE INDEX "generation_runs_userId_status_idx" ON "generation_runs"("userId", "status");
|
|
111
|
+
|
|
112
|
+
-- AddForeignKey
|
|
113
|
+
ALTER TABLE "chat_threads" ADD CONSTRAINT "chat_threads_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
114
|
+
|
|
115
|
+
-- AddForeignKey
|
|
116
|
+
ALTER TABLE "chat_messages" ADD CONSTRAINT "chat_messages_threadId_fkey" FOREIGN KEY ("threadId") REFERENCES "chat_threads"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
117
|
+
|
|
118
|
+
-- AddForeignKey
|
|
119
|
+
ALTER TABLE "chat_runs" ADD CONSTRAINT "chat_runs_threadId_fkey" FOREIGN KEY ("threadId") REFERENCES "chat_threads"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
120
|
+
|
|
121
|
+
-- AddForeignKey
|
|
122
|
+
ALTER TABLE "chat_interrupts" ADD CONSTRAINT "chat_interrupts_runId_fkey" FOREIGN KEY ("runId") REFERENCES "chat_runs"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
123
|
+
|
|
124
|
+
-- AddForeignKey
|
|
125
|
+
ALTER TABLE "chat_interrupts" ADD CONSTRAINT "chat_interrupts_threadId_fkey" FOREIGN KEY ("threadId") REFERENCES "chat_threads"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
126
|
+
|
|
127
|
+
-- AddForeignKey
|
|
128
|
+
ALTER TABLE "generation_runs" ADD CONSTRAINT "generation_runs_threadId_fkey" FOREIGN KEY ("threadId") REFERENCES "chat_threads"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
129
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
-- CreateTable
|
|
2
|
+
CREATE TABLE "monitoring_request_logs" (
|
|
3
|
+
"id" TEXT NOT NULL,
|
|
4
|
+
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
5
|
+
"method" VARCHAR(10) NOT NULL,
|
|
6
|
+
"path" VARCHAR(512) NOT NULL,
|
|
7
|
+
"status" INTEGER NOT NULL,
|
|
8
|
+
"duration" INTEGER NOT NULL,
|
|
9
|
+
"ip" VARCHAR(45),
|
|
10
|
+
"userAgent" TEXT,
|
|
11
|
+
|
|
12
|
+
CONSTRAINT "monitoring_request_logs_pkey" PRIMARY KEY ("id")
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
-- CreateTable
|
|
16
|
+
CREATE TABLE "monitoring_error_reports" (
|
|
17
|
+
"id" TEXT NOT NULL,
|
|
18
|
+
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
19
|
+
"errorName" VARCHAR(128) NOT NULL,
|
|
20
|
+
"errorMessage" TEXT NOT NULL,
|
|
21
|
+
"stackTrace" TEXT,
|
|
22
|
+
"count" INTEGER NOT NULL DEFAULT 1,
|
|
23
|
+
"firstSeen" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
24
|
+
"lastSeen" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
25
|
+
"context" JSONB NOT NULL DEFAULT '{}',
|
|
26
|
+
|
|
27
|
+
CONSTRAINT "monitoring_error_reports_pkey" PRIMARY KEY ("id")
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
-- CreateTable
|
|
31
|
+
CREATE TABLE "monitoring_metrics_snapshots" (
|
|
32
|
+
"id" TEXT NOT NULL,
|
|
33
|
+
"recordedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
34
|
+
"metricName" VARCHAR(256) NOT NULL,
|
|
35
|
+
"count" INTEGER NOT NULL,
|
|
36
|
+
"avg" DOUBLE PRECISION NOT NULL,
|
|
37
|
+
"p50" DOUBLE PRECISION NOT NULL,
|
|
38
|
+
"p95" DOUBLE PRECISION NOT NULL,
|
|
39
|
+
"p99" DOUBLE PRECISION NOT NULL,
|
|
40
|
+
"min" DOUBLE PRECISION NOT NULL,
|
|
41
|
+
"max" DOUBLE PRECISION NOT NULL,
|
|
42
|
+
|
|
43
|
+
CONSTRAINT "monitoring_metrics_snapshots_pkey" PRIMARY KEY ("id")
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- CreateTable
|
|
47
|
+
CREATE TABLE "monitoring_health_snapshots" (
|
|
48
|
+
"id" TEXT NOT NULL,
|
|
49
|
+
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
50
|
+
"status" VARCHAR(16) NOT NULL,
|
|
51
|
+
"uptimeSeconds" DOUBLE PRECISION NOT NULL,
|
|
52
|
+
"memoryUsage" JSONB NOT NULL,
|
|
53
|
+
"dbStatus" VARCHAR(16) NOT NULL,
|
|
54
|
+
"dbLatencyMs" INTEGER,
|
|
55
|
+
|
|
56
|
+
CONSTRAINT "monitoring_health_snapshots_pkey" PRIMARY KEY ("id")
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
-- CreateIndex
|
|
60
|
+
CREATE INDEX "idx_request_log_timestamp" ON "monitoring_request_logs"("timestamp");
|
|
61
|
+
|
|
62
|
+
-- CreateIndex
|
|
63
|
+
CREATE INDEX "idx_request_log_path_ts" ON "monitoring_request_logs"("path", "timestamp");
|
|
64
|
+
|
|
65
|
+
-- CreateIndex
|
|
66
|
+
CREATE INDEX "idx_error_report_name" ON "monitoring_error_reports"("errorName");
|
|
67
|
+
|
|
68
|
+
-- CreateIndex
|
|
69
|
+
CREATE INDEX "idx_error_report_last_seen" ON "monitoring_error_reports"("lastSeen");
|
|
70
|
+
|
|
71
|
+
-- CreateIndex
|
|
72
|
+
CREATE INDEX "idx_metrics_name_ts" ON "monitoring_metrics_snapshots"("metricName", "recordedAt");
|
|
73
|
+
|
|
74
|
+
-- CreateIndex
|
|
75
|
+
CREATE INDEX "idx_health_snapshot_ts" ON "monitoring_health_snapshots"("timestamp");
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
-- AlterTable
|
|
2
|
+
ALTER TABLE "projects" ADD COLUMN "workspaceId" TEXT;
|
|
3
|
+
|
|
4
|
+
-- CreateTable
|
|
5
|
+
CREATE TABLE "workspaces" (
|
|
6
|
+
"id" TEXT NOT NULL,
|
|
7
|
+
"name" TEXT NOT NULL,
|
|
8
|
+
"icon" TEXT NOT NULL DEFAULT 'folder',
|
|
9
|
+
"description" TEXT,
|
|
10
|
+
"ownerId" TEXT NOT NULL,
|
|
11
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
12
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
13
|
+
|
|
14
|
+
CONSTRAINT "workspaces_pkey" PRIMARY KEY ("id")
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
-- CreateTable
|
|
18
|
+
CREATE TABLE "workspace_members" (
|
|
19
|
+
"id" TEXT NOT NULL,
|
|
20
|
+
"workspaceId" TEXT NOT NULL,
|
|
21
|
+
"userId" TEXT NOT NULL,
|
|
22
|
+
"role" TEXT NOT NULL DEFAULT 'member',
|
|
23
|
+
"status" TEXT NOT NULL DEFAULT 'invited',
|
|
24
|
+
"joinedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
25
|
+
|
|
26
|
+
CONSTRAINT "workspace_members_pkey" PRIMARY KEY ("id")
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
-- CreateIndex
|
|
30
|
+
CREATE INDEX "workspaces_ownerId_idx" ON "workspaces"("ownerId");
|
|
31
|
+
|
|
32
|
+
-- CreateIndex
|
|
33
|
+
CREATE INDEX "workspace_members_workspaceId_idx" ON "workspace_members"("workspaceId");
|
|
34
|
+
|
|
35
|
+
-- CreateIndex
|
|
36
|
+
CREATE INDEX "workspace_members_userId_idx" ON "workspace_members"("userId");
|
|
37
|
+
|
|
38
|
+
-- CreateIndex
|
|
39
|
+
CREATE UNIQUE INDEX "workspace_members_workspaceId_userId_key" ON "workspace_members"("workspaceId", "userId");
|
|
40
|
+
|
|
41
|
+
-- CreateIndex
|
|
42
|
+
CREATE INDEX "projects_workspaceId_idx" ON "projects"("workspaceId");
|
|
43
|
+
|
|
44
|
+
-- AddForeignKey
|
|
45
|
+
ALTER TABLE "projects" ADD CONSTRAINT "projects_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
46
|
+
|
|
47
|
+
-- AddForeignKey
|
|
48
|
+
ALTER TABLE "workspaces" ADD CONSTRAINT "workspaces_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
49
|
+
|
|
50
|
+
-- AddForeignKey
|
|
51
|
+
ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
52
|
+
|
|
53
|
+
-- AddForeignKey
|
|
54
|
+
ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
55
|
+
|
|
56
|
+
-- Backfill: 为每个有项目且 workspaceId 为 null 的 owner 创建一个默认工作空间
|
|
57
|
+
INSERT INTO "workspaces" ("id", "name", "icon", "description", "ownerId", "createdAt", "updatedAt")
|
|
58
|
+
SELECT
|
|
59
|
+
gen_random_uuid()::text,
|
|
60
|
+
COALESCE(u."username", split_part(u."email", '@', 1)) || ' 的工作空间',
|
|
61
|
+
'folder',
|
|
62
|
+
'系统自动创建的默认工作空间',
|
|
63
|
+
p."ownerId",
|
|
64
|
+
NOW(),
|
|
65
|
+
NOW()
|
|
66
|
+
FROM (SELECT DISTINCT "ownerId" FROM "projects" WHERE "workspaceId" IS NULL) p
|
|
67
|
+
JOIN "users" u ON u."id" = p."ownerId"
|
|
68
|
+
WHERE NOT EXISTS (
|
|
69
|
+
SELECT 1 FROM "workspaces" w
|
|
70
|
+
WHERE w."ownerId" = p."ownerId" AND w."description" = '系统自动创建的默认工作空间'
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
-- Backfill: 回填 project.workspaceId
|
|
74
|
+
UPDATE "projects"
|
|
75
|
+
SET "workspaceId" = (
|
|
76
|
+
SELECT "id" FROM "workspaces"
|
|
77
|
+
WHERE "ownerId" = "projects"."ownerId"
|
|
78
|
+
AND "description" = '系统自动创建的默认工作空间'
|
|
79
|
+
LIMIT 1
|
|
80
|
+
)
|
|
81
|
+
WHERE "workspaceId" IS NULL;
|
|
82
|
+
|
|
83
|
+
-- Backfill: 把每个默认工作空间的 owner 加入为 owner 角色
|
|
84
|
+
INSERT INTO "workspace_members" ("id", "workspaceId", "userId", "role", "status", "joinedAt")
|
|
85
|
+
SELECT gen_random_uuid()::text, w."id", w."ownerId", 'owner', 'active', NOW()
|
|
86
|
+
FROM "workspaces" w
|
|
87
|
+
WHERE NOT EXISTS (
|
|
88
|
+
SELECT 1 FROM "workspace_members" wm WHERE wm."workspaceId" = w."id"
|
|
89
|
+
);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "SubscriptionStatus" AS ENUM ('TRIALING', 'ACTIVE', 'PAST_DUE', 'CANCELED', 'EXPIRED', 'UNPAID');
|
|
3
|
+
|
|
4
|
+
-- CreateEnum
|
|
5
|
+
CREATE TYPE "UsageMetric" AS ENUM ('AI_CALL', 'STORAGE_MB', 'SEAT');
|
|
6
|
+
|
|
7
|
+
-- CreateTable
|
|
8
|
+
CREATE TABLE "plans" (
|
|
9
|
+
"id" TEXT NOT NULL,
|
|
10
|
+
"name" TEXT NOT NULL,
|
|
11
|
+
"displayName" TEXT NOT NULL,
|
|
12
|
+
"description" TEXT,
|
|
13
|
+
"priceMonthly" INTEGER NOT NULL DEFAULT 0,
|
|
14
|
+
"priceYearly" INTEGER,
|
|
15
|
+
"currency" TEXT NOT NULL DEFAULT 'usd',
|
|
16
|
+
"maxSeats" INTEGER,
|
|
17
|
+
"maxWorkspaces" INTEGER,
|
|
18
|
+
"aiQuotaMonthly" INTEGER,
|
|
19
|
+
"features" JSONB NOT NULL,
|
|
20
|
+
"isPublic" BOOLEAN NOT NULL DEFAULT true,
|
|
21
|
+
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
|
22
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
23
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
24
|
+
|
|
25
|
+
CONSTRAINT "plans_pkey" PRIMARY KEY ("id")
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
-- CreateTable
|
|
29
|
+
CREATE TABLE "subscriptions" (
|
|
30
|
+
"id" TEXT NOT NULL,
|
|
31
|
+
"workspaceId" TEXT NOT NULL,
|
|
32
|
+
"planId" TEXT NOT NULL,
|
|
33
|
+
"status" "SubscriptionStatus" NOT NULL DEFAULT 'ACTIVE',
|
|
34
|
+
"seats" INTEGER NOT NULL DEFAULT 1,
|
|
35
|
+
"stripeCustomerId" TEXT,
|
|
36
|
+
"stripeSubscriptionId" TEXT,
|
|
37
|
+
"stripePriceId" TEXT,
|
|
38
|
+
"currentPeriodStart" TIMESTAMP(3),
|
|
39
|
+
"currentPeriodEnd" TIMESTAMP(3),
|
|
40
|
+
"cancelAtPeriodEnd" BOOLEAN NOT NULL DEFAULT false,
|
|
41
|
+
"canceledAt" TIMESTAMP(3),
|
|
42
|
+
"trialEndsAt" TIMESTAMP(3),
|
|
43
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
44
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
45
|
+
|
|
46
|
+
CONSTRAINT "subscriptions_pkey" PRIMARY KEY ("id")
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
-- CreateTable
|
|
50
|
+
CREATE TABLE "usage_records" (
|
|
51
|
+
"id" TEXT NOT NULL,
|
|
52
|
+
"workspaceId" TEXT NOT NULL,
|
|
53
|
+
"metric" "UsageMetric" NOT NULL,
|
|
54
|
+
"period" TEXT NOT NULL,
|
|
55
|
+
"count" INTEGER NOT NULL DEFAULT 0,
|
|
56
|
+
"resetAt" TIMESTAMP(3) NOT NULL,
|
|
57
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
58
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
59
|
+
|
|
60
|
+
CONSTRAINT "usage_records_pkey" PRIMARY KEY ("id")
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
-- CreateTable
|
|
64
|
+
CREATE TABLE "ai_usage_logs" (
|
|
65
|
+
"id" TEXT NOT NULL,
|
|
66
|
+
"workspaceId" TEXT NOT NULL,
|
|
67
|
+
"userId" TEXT NOT NULL,
|
|
68
|
+
"source" TEXT NOT NULL DEFAULT 'byok',
|
|
69
|
+
"provider" TEXT NOT NULL,
|
|
70
|
+
"model" TEXT NOT NULL,
|
|
71
|
+
"inputTokens" INTEGER NOT NULL DEFAULT 0,
|
|
72
|
+
"outputTokens" INTEGER NOT NULL DEFAULT 0,
|
|
73
|
+
"costUsd" DECIMAL(10,6),
|
|
74
|
+
"success" BOOLEAN NOT NULL DEFAULT true,
|
|
75
|
+
"errorMessage" TEXT,
|
|
76
|
+
"durationMs" INTEGER,
|
|
77
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
78
|
+
|
|
79
|
+
CONSTRAINT "ai_usage_logs_pkey" PRIMARY KEY ("id")
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
-- CreateIndex
|
|
83
|
+
CREATE UNIQUE INDEX "plans_name_key" ON "plans"("name");
|
|
84
|
+
|
|
85
|
+
-- CreateIndex
|
|
86
|
+
CREATE UNIQUE INDEX "subscriptions_workspaceId_key" ON "subscriptions"("workspaceId");
|
|
87
|
+
|
|
88
|
+
-- CreateIndex
|
|
89
|
+
CREATE INDEX "usage_records_workspaceId_metric_idx" ON "usage_records"("workspaceId", "metric");
|
|
90
|
+
|
|
91
|
+
-- CreateIndex
|
|
92
|
+
CREATE UNIQUE INDEX "usage_records_workspaceId_metric_period_key" ON "usage_records"("workspaceId", "metric", "period");
|
|
93
|
+
|
|
94
|
+
-- CreateIndex
|
|
95
|
+
CREATE INDEX "ai_usage_logs_workspaceId_createdAt_idx" ON "ai_usage_logs"("workspaceId", "createdAt");
|
|
96
|
+
|
|
97
|
+
-- CreateIndex
|
|
98
|
+
CREATE INDEX "ai_usage_logs_userId_createdAt_idx" ON "ai_usage_logs"("userId", "createdAt");
|
|
99
|
+
|
|
100
|
+
-- CreateIndex
|
|
101
|
+
CREATE INDEX "ai_usage_logs_workspaceId_source_createdAt_idx" ON "ai_usage_logs"("workspaceId", "source", "createdAt");
|
|
102
|
+
|
|
103
|
+
-- CreateIndex
|
|
104
|
+
CREATE INDEX "ai_usage_logs_workspaceId_success_createdAt_idx" ON "ai_usage_logs"("workspaceId", "success", "createdAt");
|
|
105
|
+
|
|
106
|
+
-- AddForeignKey
|
|
107
|
+
ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
108
|
+
|
|
109
|
+
-- AddForeignKey
|
|
110
|
+
ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_planId_fkey" FOREIGN KEY ("planId") REFERENCES "plans"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
111
|
+
|
|
112
|
+
-- AddForeignKey
|
|
113
|
+
ALTER TABLE "usage_records" ADD CONSTRAINT "usage_records_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
114
|
+
|
|
115
|
+
-- AddForeignKey
|
|
116
|
+
ALTER TABLE "ai_usage_logs" ADD CONSTRAINT "ai_usage_logs_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
117
|
+
|
|
118
|
+
-- AddForeignKey
|
|
119
|
+
ALTER TABLE "ai_usage_logs" ADD CONSTRAINT "ai_usage_logs_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
-- 幂等化迁移:子项目 3 新增 Invoice/StripeEvent 表 + Plan/User/Subscription 字段
|
|
2
|
+
|
|
3
|
+
-- 为 Plan 表添加 Stripe 字段(幂等:列已存在则跳过)
|
|
4
|
+
DO $$
|
|
5
|
+
BEGIN
|
|
6
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripeProductId') THEN
|
|
7
|
+
ALTER TABLE "plans" ADD COLUMN "stripeProductId" TEXT;
|
|
8
|
+
END IF;
|
|
9
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripePriceIdUsd') THEN
|
|
10
|
+
ALTER TABLE "plans" ADD COLUMN "stripePriceIdUsd" TEXT;
|
|
11
|
+
END IF;
|
|
12
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripePriceIdCny') THEN
|
|
13
|
+
ALTER TABLE "plans" ADD COLUMN "stripePriceIdCny" TEXT;
|
|
14
|
+
END IF;
|
|
15
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripePriceIdUsdYearly') THEN
|
|
16
|
+
ALTER TABLE "plans" ADD COLUMN "stripePriceIdUsdYearly" TEXT;
|
|
17
|
+
END IF;
|
|
18
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripePriceIdCnyYearly') THEN
|
|
19
|
+
ALTER TABLE "plans" ADD COLUMN "stripePriceIdCnyYearly" TEXT;
|
|
20
|
+
END IF;
|
|
21
|
+
END $$;
|
|
22
|
+
|
|
23
|
+
-- 为 User 表添加 preferredCurrency(幂等)
|
|
24
|
+
DO $$
|
|
25
|
+
BEGIN
|
|
26
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'preferredCurrency') THEN
|
|
27
|
+
ALTER TABLE "users" ADD COLUMN "preferredCurrency" TEXT NOT NULL DEFAULT 'usd';
|
|
28
|
+
END IF;
|
|
29
|
+
END $$;
|
|
30
|
+
|
|
31
|
+
-- 创建 Invoice 表(幂等)
|
|
32
|
+
CREATE TABLE IF NOT EXISTS "invoices" (
|
|
33
|
+
"id" TEXT NOT NULL,
|
|
34
|
+
"subscriptionId" TEXT NOT NULL,
|
|
35
|
+
"stripeInvoiceId" TEXT NOT NULL,
|
|
36
|
+
"stripeEventId" TEXT,
|
|
37
|
+
"amountDue" INTEGER NOT NULL,
|
|
38
|
+
"amountPaid" INTEGER NOT NULL,
|
|
39
|
+
"currency" TEXT NOT NULL DEFAULT 'usd',
|
|
40
|
+
"status" TEXT NOT NULL,
|
|
41
|
+
"pdfUrl" TEXT,
|
|
42
|
+
"hostedInvoiceUrl" TEXT,
|
|
43
|
+
"periodStart" TIMESTAMP(3) NOT NULL,
|
|
44
|
+
"periodEnd" TIMESTAMP(3) NOT NULL,
|
|
45
|
+
"paidAt" TIMESTAMP(3),
|
|
46
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
47
|
+
|
|
48
|
+
CONSTRAINT "invoices_pkey" PRIMARY KEY ("id")
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
-- 创建 StripeEvent 表(幂等)
|
|
52
|
+
CREATE TABLE IF NOT EXISTS "stripe_events" (
|
|
53
|
+
"id" TEXT NOT NULL,
|
|
54
|
+
"stripeEventId" TEXT NOT NULL,
|
|
55
|
+
"type" TEXT NOT NULL,
|
|
56
|
+
"status" TEXT NOT NULL DEFAULT 'processing',
|
|
57
|
+
"payload" JSONB,
|
|
58
|
+
"errorMessage" TEXT,
|
|
59
|
+
"processedAt" TIMESTAMP(3),
|
|
60
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
61
|
+
|
|
62
|
+
CONSTRAINT "stripe_events_pkey" PRIMARY KEY ("id")
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
-- 创建唯一索引(幂等)
|
|
66
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "invoices_stripeInvoiceId_key" ON "invoices"("stripeInvoiceId");
|
|
67
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "stripe_events_stripeEventId_key" ON "stripe_events"("stripeEventId");
|
|
68
|
+
|
|
69
|
+
-- 创建普通索引(幂等)
|
|
70
|
+
CREATE INDEX IF NOT EXISTS "invoices_subscriptionId_createdAt_idx" ON "invoices"("subscriptionId", "createdAt");
|
|
71
|
+
CREATE INDEX IF NOT EXISTS "stripe_events_type_createdAt_idx" ON "stripe_events"("type", "createdAt");
|
|
72
|
+
|
|
73
|
+
-- 添加外键(幂等:已存在则跳过)
|
|
74
|
+
DO $$
|
|
75
|
+
BEGIN
|
|
76
|
+
IF NOT EXISTS (
|
|
77
|
+
SELECT 1 FROM information_schema.table_constraints
|
|
78
|
+
WHERE constraint_name = 'invoices_subscriptionId_fkey'
|
|
79
|
+
) THEN
|
|
80
|
+
ALTER TABLE "invoices"
|
|
81
|
+
ADD CONSTRAINT "invoices_subscriptionId_fkey"
|
|
82
|
+
FOREIGN KEY ("subscriptionId")
|
|
83
|
+
REFERENCES "subscriptions"("id")
|
|
84
|
+
ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
85
|
+
END IF;
|
|
86
|
+
END $$;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- 子项目 3: stripeSubscriptionId 添加 @unique 约束
|
|
2
|
+
-- 用于 webhook 反查订阅(subscription.ts syncSubscriptionFromStripe)
|
|
3
|
+
-- 字段可空(free plan 无 Stripe 订阅),但非空值必须唯一
|
|
4
|
+
|
|
5
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "subscriptions_stripeSubscriptionId_key"
|
|
6
|
+
ON "subscriptions"("stripeSubscriptionId");
|