@allbluecn/database 0.4.4 → 0.4.6

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.
Files changed (25) hide show
  1. package/migrations/20260804174501_init/migration.sql +1764 -0
  2. package/migrations/20260805174545_add_story_activity_is_ai/migration.sql +2 -0
  3. package/migrations/20260806035202_story_archived/migration.sql +6 -0
  4. package/migrations/20260806061157_builtin_independent_project/migration.sql +26 -0
  5. package/migrations/20260806144250_tanstack_ai_persistence/migration.sql +129 -0
  6. package/migrations/20260809080004_add_monitoring_observability/migration.sql +75 -0
  7. package/migrations/20260811112502_add_workspace/migration.sql +89 -0
  8. package/migrations/20260811153719_add_hosted_ai_tables/migration.sql +119 -0
  9. package/migrations/20260812043944_add_billing_tables/migration.sql +86 -0
  10. package/migrations/20260813040200_sub3_stripe_subscription_unique/migration.sql +6 -0
  11. package/migrations/20260813061345_add_enterprise_license_sso_audit_permissions/migration.sql +169 -0
  12. package/migrations/20260813132026_add_user_locale/migration.sql +2 -0
  13. package/migrations/20260814000000_add_story_composite_index/migration.sql +2 -0
  14. package/migrations/20260814161622_add_user_default_workspace_id/migration.sql +2 -0
  15. package/migrations/20260815025746_p2_seat_addon/migration.sql +15 -0
  16. package/migrations/20260815120000_creem_billing_neutralization/migration.sql +122 -0
  17. package/migrations/20260816212027_compliance_content/migration.sql +3 -0
  18. package/migrations/20260817204928_p6_user_agents_skills/migration.sql +57 -0
  19. package/migrations/20260818094327_user_level_subscription/migration.sql +43 -0
  20. package/migrations/20260819014924_team_membership_invitation/migration.sql +65 -0
  21. package/migrations/migration_lock.toml +3 -0
  22. package/package.json +8 -3
  23. package/prisma.config.ts +18 -0
  24. package/schema.prisma +2014 -0
  25. package/schema.sqlite.prisma +1584 -0
@@ -0,0 +1,169 @@
1
+ -- CreateTable
2
+ CREATE TABLE "license_keys" (
3
+ "id" TEXT NOT NULL,
4
+ "key" TEXT NOT NULL,
5
+ "signature" TEXT NOT NULL,
6
+ "payload" JSONB NOT NULL,
7
+ "customerId" TEXT NOT NULL,
8
+ "customerName" TEXT NOT NULL,
9
+ "customerEmail" TEXT NOT NULL,
10
+ "plan" TEXT NOT NULL DEFAULT 'enterprise',
11
+ "seats" INTEGER NOT NULL,
12
+ "features" JSONB NOT NULL,
13
+ "issuedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
14
+ "expiresAt" TIMESTAMP(3),
15
+ "revokedAt" TIMESTAMP(3),
16
+ "issuedById" TEXT,
17
+
18
+ CONSTRAINT "license_keys_pkey" PRIMARY KEY ("id")
19
+ );
20
+
21
+ -- CreateTable
22
+ CREATE TABLE "license_activations" (
23
+ "id" TEXT NOT NULL,
24
+ "licenseId" TEXT NOT NULL,
25
+ "instanceId" TEXT NOT NULL,
26
+ "fingerprint" TEXT NOT NULL,
27
+ "activatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
28
+ "lastSeenAt" TIMESTAMP(3),
29
+ "revokedAt" TIMESTAMP(3),
30
+
31
+ CONSTRAINT "license_activations_pkey" PRIMARY KEY ("id")
32
+ );
33
+
34
+ -- CreateTable
35
+ CREATE TABLE "audit_logs" (
36
+ "id" TEXT NOT NULL,
37
+ "workspaceId" TEXT,
38
+ "userId" TEXT,
39
+ "action" TEXT NOT NULL,
40
+ "resourceType" TEXT NOT NULL,
41
+ "resourceId" TEXT,
42
+ "metadata" JSONB NOT NULL,
43
+ "ip" TEXT,
44
+ "userAgent" TEXT,
45
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
46
+
47
+ CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id")
48
+ );
49
+
50
+ -- CreateTable
51
+ CREATE TABLE "sso_providers" (
52
+ "id" TEXT NOT NULL,
53
+ "workspaceId" TEXT NOT NULL,
54
+ "type" TEXT NOT NULL,
55
+ "name" TEXT NOT NULL,
56
+ "config" JSONB NOT NULL,
57
+ "domain" TEXT,
58
+ "enabled" BOOLEAN NOT NULL DEFAULT true,
59
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
60
+ "updatedAt" TIMESTAMP(3) NOT NULL,
61
+
62
+ CONSTRAINT "sso_providers_pkey" PRIMARY KEY ("id")
63
+ );
64
+
65
+ -- CreateTable
66
+ CREATE TABLE "sso_mappings" (
67
+ "id" TEXT NOT NULL,
68
+ "providerId" TEXT NOT NULL,
69
+ "externalId" TEXT NOT NULL,
70
+ "userId" TEXT NOT NULL,
71
+ "email" TEXT,
72
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
73
+
74
+ CONSTRAINT "sso_mappings_pkey" PRIMARY KEY ("id")
75
+ );
76
+
77
+ -- CreateTable
78
+ CREATE TABLE "custom_roles" (
79
+ "id" TEXT NOT NULL,
80
+ "workspaceId" TEXT NOT NULL,
81
+ "name" TEXT NOT NULL,
82
+ "permissions" JSONB NOT NULL,
83
+ "isSystem" BOOLEAN NOT NULL DEFAULT false,
84
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
85
+ "updatedAt" TIMESTAMP(3) NOT NULL,
86
+
87
+ CONSTRAINT "custom_roles_pkey" PRIMARY KEY ("id")
88
+ );
89
+
90
+ -- CreateTable
91
+ CREATE TABLE "role_assignments" (
92
+ "id" TEXT NOT NULL,
93
+ "roleId" TEXT NOT NULL,
94
+ "userId" TEXT NOT NULL,
95
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
96
+
97
+ CONSTRAINT "role_assignments_pkey" PRIMARY KEY ("id")
98
+ );
99
+
100
+ -- CreateTable
101
+ CREATE TABLE "compliance_exports" (
102
+ "id" TEXT NOT NULL,
103
+ "type" TEXT NOT NULL,
104
+ "scope" TEXT NOT NULL,
105
+ "targetId" TEXT NOT NULL,
106
+ "status" TEXT NOT NULL DEFAULT 'pending',
107
+ "downloadUrl" TEXT,
108
+ "expiresAt" TIMESTAMP(3),
109
+ "error" TEXT,
110
+ "requestedBy" TEXT,
111
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
112
+ "completedAt" TIMESTAMP(3),
113
+
114
+ CONSTRAINT "compliance_exports_pkey" PRIMARY KEY ("id")
115
+ );
116
+
117
+ -- CreateIndex
118
+ CREATE UNIQUE INDEX "license_keys_key_key" ON "license_keys"("key");
119
+
120
+ -- CreateIndex
121
+ CREATE UNIQUE INDEX "license_activations_instanceId_key" ON "license_activations"("instanceId");
122
+
123
+ -- CreateIndex
124
+ CREATE INDEX "audit_logs_workspaceId_createdAt_idx" ON "audit_logs"("workspaceId", "createdAt");
125
+
126
+ -- CreateIndex
127
+ CREATE INDEX "audit_logs_userId_createdAt_idx" ON "audit_logs"("userId", "createdAt");
128
+
129
+ -- CreateIndex
130
+ CREATE INDEX "audit_logs_action_idx" ON "audit_logs"("action");
131
+
132
+ -- CreateIndex
133
+ CREATE UNIQUE INDEX "sso_providers_workspaceId_type_domain_key" ON "sso_providers"("workspaceId", "type", "domain");
134
+
135
+ -- CreateIndex
136
+ CREATE UNIQUE INDEX "sso_mappings_providerId_externalId_key" ON "sso_mappings"("providerId", "externalId");
137
+
138
+ -- CreateIndex
139
+ CREATE UNIQUE INDEX "custom_roles_workspaceId_name_key" ON "custom_roles"("workspaceId", "name");
140
+
141
+ -- CreateIndex
142
+ CREATE UNIQUE INDEX "role_assignments_roleId_userId_key" ON "role_assignments"("roleId", "userId");
143
+
144
+ -- AddForeignKey
145
+ ALTER TABLE "license_keys" ADD CONSTRAINT "license_keys_issuedById_fkey" FOREIGN KEY ("issuedById") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
146
+
147
+ -- AddForeignKey
148
+ ALTER TABLE "license_activations" ADD CONSTRAINT "license_activations_licenseId_fkey" FOREIGN KEY ("licenseId") REFERENCES "license_keys"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
149
+
150
+ -- AddForeignKey
151
+ ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE SET NULL ON UPDATE CASCADE;
152
+
153
+ -- AddForeignKey
154
+ ALTER TABLE "sso_providers" ADD CONSTRAINT "sso_providers_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
155
+
156
+ -- AddForeignKey
157
+ ALTER TABLE "sso_mappings" ADD CONSTRAINT "sso_mappings_providerId_fkey" FOREIGN KEY ("providerId") REFERENCES "sso_providers"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
158
+
159
+ -- AddForeignKey
160
+ ALTER TABLE "sso_mappings" ADD CONSTRAINT "sso_mappings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
161
+
162
+ -- AddForeignKey
163
+ ALTER TABLE "custom_roles" ADD CONSTRAINT "custom_roles_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
164
+
165
+ -- AddForeignKey
166
+ ALTER TABLE "role_assignments" ADD CONSTRAINT "role_assignments_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "custom_roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
167
+
168
+ -- AddForeignKey
169
+ ALTER TABLE "role_assignments" ADD CONSTRAINT "role_assignments_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
1
+ -- AlterTable
2
+ ALTER TABLE "users" ADD COLUMN "locale" TEXT;
@@ -0,0 +1,2 @@
1
+ -- CreateIndex
2
+ CREATE INDEX "stories_archived_state_createdAt_idx" ON "stories"("archived", "state", "createdAt");
@@ -0,0 +1,2 @@
1
+ -- AlterTable
2
+ ALTER TABLE "users" ADD COLUMN "defaultWorkspaceId" TEXT;
@@ -0,0 +1,15 @@
1
+ -- P2 席位加购:Subscription.addonSeats + Plan 席位价格字段
2
+ DO $$ BEGIN
3
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'subscriptions' AND column_name = 'addonSeats') THEN
4
+ ALTER TABLE "subscriptions" ADD COLUMN "addonSeats" INTEGER NOT NULL DEFAULT 0;
5
+ END IF;
6
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'seatPriceMonthly') THEN
7
+ ALTER TABLE "plans" ADD COLUMN "seatPriceMonthly" INTEGER;
8
+ END IF;
9
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripeSeatPriceIdUsd') THEN
10
+ ALTER TABLE "plans" ADD COLUMN "stripeSeatPriceIdUsd" TEXT;
11
+ END IF;
12
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripeSeatPriceIdCny') THEN
13
+ ALTER TABLE "plans" ADD COLUMN "stripeSeatPriceIdCny" TEXT;
14
+ END IF;
15
+ END $$;
@@ -0,0 +1,122 @@
1
+ -- 计费字段中立化:Stripe → Provider(配合 Creem 计费集成)
2
+ -- 无存量数据,直接 RENAME/DROP/ADD,幂等化
3
+
4
+ -- ── Plan 表 ──
5
+
6
+ -- 删除 stripeProductId(Creem 无独立产品概念,价格点即产品)
7
+ DO $$ BEGIN
8
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripeProductId') THEN
9
+ ALTER TABLE "plans" DROP COLUMN "stripeProductId";
10
+ END IF;
11
+ END $$;
12
+
13
+ -- 重命名 Plan 价格字段
14
+ DO $$ BEGIN
15
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripePriceIdUsd') THEN
16
+ ALTER TABLE "plans" RENAME COLUMN "stripePriceIdUsd" TO "providerPriceIdUsd";
17
+ END IF;
18
+ END $$;
19
+
20
+ DO $$ BEGIN
21
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripePriceIdCny') THEN
22
+ ALTER TABLE "plans" RENAME COLUMN "stripePriceIdCny" TO "providerPriceIdCny";
23
+ END IF;
24
+ END $$;
25
+
26
+ DO $$ BEGIN
27
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripePriceIdUsdYearly') THEN
28
+ ALTER TABLE "plans" RENAME COLUMN "stripePriceIdUsdYearly" TO "providerPriceIdUsdYearly";
29
+ END IF;
30
+ END $$;
31
+
32
+ DO $$ BEGIN
33
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripePriceIdCnyYearly') THEN
34
+ ALTER TABLE "plans" RENAME COLUMN "stripePriceIdCnyYearly" TO "providerPriceIdCnyYearly";
35
+ END IF;
36
+ END $$;
37
+
38
+ DO $$ BEGIN
39
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripeSeatPriceIdUsd') THEN
40
+ ALTER TABLE "plans" RENAME COLUMN "stripeSeatPriceIdUsd" TO "providerSeatPriceIdUsd";
41
+ END IF;
42
+ END $$;
43
+
44
+ DO $$ BEGIN
45
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'plans' AND column_name = 'stripeSeatPriceIdCny') THEN
46
+ ALTER TABLE "plans" RENAME COLUMN "stripeSeatPriceIdCny" TO "providerSeatPriceIdCny";
47
+ END IF;
48
+ END $$;
49
+
50
+ -- ── Subscription 表 ──
51
+
52
+ DO $$ BEGIN
53
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'subscriptions' AND column_name = 'stripeCustomerId') THEN
54
+ ALTER TABLE "subscriptions" RENAME COLUMN "stripeCustomerId" TO "providerCustomerId";
55
+ END IF;
56
+ END $$;
57
+
58
+ DO $$ BEGIN
59
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'subscriptions' AND column_name = 'stripeSubscriptionId') THEN
60
+ ALTER TABLE "subscriptions" RENAME COLUMN "stripeSubscriptionId" TO "providerSubscriptionId";
61
+ END IF;
62
+ END $$;
63
+
64
+ DO $$ BEGIN
65
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'subscriptions' AND column_name = 'stripePriceId') THEN
66
+ ALTER TABLE "subscriptions" RENAME COLUMN "stripePriceId" TO "providerPriceId";
67
+ END IF;
68
+ END $$;
69
+
70
+ -- 新增 provider 字段
71
+ DO $$ BEGIN
72
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'subscriptions' AND column_name = 'provider') THEN
73
+ ALTER TABLE "subscriptions" ADD COLUMN "provider" TEXT NOT NULL DEFAULT 'creem';
74
+ END IF;
75
+ END $$;
76
+
77
+ -- 新增 providerSeatSubscriptionId 字段
78
+ DO $$ BEGIN
79
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'subscriptions' AND column_name = 'providerSeatSubscriptionId') THEN
80
+ ALTER TABLE "subscriptions" ADD COLUMN "providerSeatSubscriptionId" TEXT;
81
+ END IF;
82
+ END $$;
83
+
84
+ -- 重建唯一索引
85
+ DROP INDEX IF EXISTS "subscriptions_stripeSubscriptionId_key";
86
+ CREATE UNIQUE INDEX IF NOT EXISTS "subscriptions_providerSubscriptionId_key" ON "subscriptions"("providerSubscriptionId");
87
+
88
+ -- ── Invoice 表 ──
89
+
90
+ DO $$ BEGIN
91
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'invoices' AND column_name = 'stripeInvoiceId') THEN
92
+ ALTER TABLE "invoices" RENAME COLUMN "stripeInvoiceId" TO "providerInvoiceId";
93
+ END IF;
94
+ END $$;
95
+
96
+ DO $$ BEGIN
97
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'invoices' AND column_name = 'stripeEventId') THEN
98
+ ALTER TABLE "invoices" RENAME COLUMN "stripeEventId" TO "providerEventId";
99
+ END IF;
100
+ END $$;
101
+
102
+ -- 重建唯一索引
103
+ DROP INDEX IF EXISTS "invoices_stripeInvoiceId_key";
104
+ CREATE UNIQUE INDEX IF NOT EXISTS "invoices_providerInvoiceId_key" ON "invoices"("providerInvoiceId");
105
+
106
+ -- ── StripeEvent → ProviderEvent ──
107
+
108
+ DROP TABLE IF EXISTS "stripe_events";
109
+
110
+ CREATE TABLE "provider_events" (
111
+ "id" TEXT PRIMARY KEY,
112
+ "providerEventId" TEXT NOT NULL,
113
+ "type" TEXT NOT NULL,
114
+ "status" TEXT NOT NULL DEFAULT 'processing',
115
+ "payload" JSONB,
116
+ "errorMessage" TEXT,
117
+ "processedAt" TIMESTAMPTZ,
118
+ "createdAt" TIMESTAMPTZ NOT NULL DEFAULT now()
119
+ );
120
+
121
+ CREATE UNIQUE INDEX IF NOT EXISTS "provider_events_providerEventId_key" ON "provider_events"("providerEventId");
122
+ CREATE INDEX IF NOT EXISTS "provider_events_type_createdAt_idx" ON "provider_events"("type", "createdAt");
@@ -0,0 +1,3 @@
1
+ -- AlterTable
2
+ ALTER TABLE "compliance_exports" ADD COLUMN "content" TEXT;
3
+
@@ -0,0 +1,57 @@
1
+ -- AlterTable
2
+ ALTER TABLE "chat_threads" ADD COLUMN "agentId" TEXT;
3
+
4
+ -- AlterTable
5
+ ALTER TABLE "plans" ADD COLUMN "agentQuota" INTEGER,
6
+ ADD COLUMN "skillQuota" INTEGER;
7
+
8
+ -- CreateTable
9
+ CREATE TABLE "user_agents" (
10
+ "id" TEXT NOT NULL,
11
+ "userId" TEXT NOT NULL,
12
+ "name" TEXT NOT NULL,
13
+ "description" TEXT,
14
+ "avatarEmoji" TEXT,
15
+ "systemPrompt" TEXT NOT NULL,
16
+ "allowedTools" JSONB NOT NULL DEFAULT '[]',
17
+ "model" TEXT,
18
+ "temperature" DOUBLE PRECISION,
19
+ "greeting" TEXT,
20
+ "suggestedPrompts" JSONB,
21
+ "knowledgeBaseIds" JSONB NOT NULL DEFAULT '[]',
22
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
23
+ "updatedAt" TIMESTAMP(3) NOT NULL,
24
+ "isDeleted" BOOLEAN NOT NULL DEFAULT false,
25
+
26
+ CONSTRAINT "user_agents_pkey" PRIMARY KEY ("id")
27
+ );
28
+
29
+ -- CreateTable
30
+ CREATE TABLE "user_skills" (
31
+ "id" TEXT NOT NULL,
32
+ "userId" TEXT NOT NULL,
33
+ "name" TEXT NOT NULL,
34
+ "title" TEXT NOT NULL,
35
+ "template" TEXT NOT NULL,
36
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
37
+ "updatedAt" TIMESTAMP(3) NOT NULL,
38
+ "isDeleted" BOOLEAN NOT NULL DEFAULT false,
39
+
40
+ CONSTRAINT "user_skills_pkey" PRIMARY KEY ("id")
41
+ );
42
+
43
+ -- CreateIndex
44
+ CREATE INDEX "user_agents_userId_idx" ON "user_agents"("userId");
45
+
46
+ -- CreateIndex
47
+ CREATE UNIQUE INDEX "user_skills_userId_name_key" ON "user_skills"("userId", "name");
48
+
49
+ -- AddForeignKey
50
+ ALTER TABLE "chat_threads" ADD CONSTRAINT "chat_threads_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "user_agents"("id") ON DELETE SET NULL ON UPDATE CASCADE;
51
+
52
+ -- AddForeignKey
53
+ ALTER TABLE "user_agents" ADD CONSTRAINT "user_agents_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
54
+
55
+ -- AddForeignKey
56
+ ALTER TABLE "user_skills" ADD CONSTRAINT "user_skills_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
57
+
@@ -0,0 +1,43 @@
1
+ -- ── 数据迁移:workspace 级订阅 → user 级(按 workspace owner 回填)──
2
+
3
+ -- ① 同 owner 多条订阅:保留 plan.sortOrder 最高,同分用 id 小者,删除其余
4
+ DELETE FROM "subscriptions" s
5
+ USING "subscriptions" s2, "workspaces" w, "workspaces" w2, "plans" p, "plans" p2
6
+ WHERE s."workspaceId" = w."id"
7
+ AND s2."workspaceId" = w2."id"
8
+ AND w."ownerId" = w2."ownerId"
9
+ AND s."planId" = p."id"
10
+ AND s2."planId" = p2."id"
11
+ AND s.id <> s2.id
12
+ AND (p2."sortOrder" > p."sortOrder" OR (p2."sortOrder" = p."sortOrder" AND s2.id < s.id));
13
+
14
+ -- ② 解除旧约束
15
+ ALTER TABLE "subscriptions" DROP CONSTRAINT IF EXISTS "subscriptions_workspaceId_fkey";
16
+ DROP INDEX IF EXISTS "subscriptions_workspaceId_key";
17
+
18
+ -- ③ 添加可空 userId 列
19
+ ALTER TABLE "subscriptions" ADD COLUMN IF NOT EXISTS "userId" TEXT;
20
+
21
+ -- ④ 添加 FK(NULL 值不受 FK 约束)
22
+ ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
23
+
24
+ -- ⑤ 按 workspace owner 回填
25
+ UPDATE "subscriptions" s
26
+ SET "userId" = w."ownerId"
27
+ FROM "workspaces" w
28
+ WHERE s."workspaceId" = w."id" AND s."userId" IS NULL;
29
+
30
+ -- ⑥ 孤儿订阅(理论不存在,防御):删除
31
+ DELETE FROM "subscriptions" WHERE "userId" IS NULL;
32
+
33
+ -- ⑦ userId 收紧为 NOT NULL
34
+ ALTER TABLE "subscriptions" ALTER COLUMN "userId" SET NOT NULL;
35
+
36
+ -- ⑧ 删除废弃列
37
+ ALTER TABLE "subscriptions" DROP COLUMN "workspaceId";
38
+
39
+ -- ⑨ 删除废弃 seats 列
40
+ ALTER TABLE "subscriptions" DROP COLUMN "seats";
41
+
42
+ -- ⑩ 创建 userId 唯一索引
43
+ CREATE UNIQUE INDEX "subscriptions_userId_key" ON "subscriptions"("userId");
@@ -0,0 +1,65 @@
1
+ -- DropForeignKey
2
+ ALTER TABLE "workspace_members" DROP CONSTRAINT "workspace_members_userId_fkey";
3
+
4
+ -- DropForeignKey
5
+ ALTER TABLE "workspace_members" DROP CONSTRAINT "workspace_members_workspaceId_fkey";
6
+
7
+ -- AlterTable
8
+ ALTER TABLE "plans" ADD COLUMN "maxGuestSeats" INTEGER;
9
+
10
+ -- DropTable
11
+ DROP TABLE "workspace_members";
12
+
13
+ -- CreateTable
14
+ CREATE TABLE "team_memberships" (
15
+ "id" TEXT NOT NULL,
16
+ "ownerId" TEXT NOT NULL,
17
+ "memberId" TEXT NOT NULL,
18
+ "seatType" TEXT NOT NULL DEFAULT 'member',
19
+ "joinedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
20
+
21
+ CONSTRAINT "team_memberships_pkey" PRIMARY KEY ("id")
22
+ );
23
+
24
+ -- CreateTable
25
+ CREATE TABLE "team_invitations" (
26
+ "id" TEXT NOT NULL,
27
+ "ownerId" TEXT NOT NULL,
28
+ "email" TEXT NOT NULL,
29
+ "seatType" TEXT NOT NULL DEFAULT 'member',
30
+ "token" TEXT NOT NULL,
31
+ "channel" TEXT NOT NULL DEFAULT 'email',
32
+ "status" TEXT NOT NULL DEFAULT 'pending',
33
+ "expiresAt" TIMESTAMP(3) NOT NULL,
34
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
35
+ "acceptedBy" TEXT,
36
+
37
+ CONSTRAINT "team_invitations_pkey" PRIMARY KEY ("id")
38
+ );
39
+
40
+ -- CreateIndex
41
+ CREATE INDEX "team_memberships_ownerId_idx" ON "team_memberships"("ownerId");
42
+
43
+ -- CreateIndex
44
+ CREATE UNIQUE INDEX "team_memberships_memberId_key" ON "team_memberships"("memberId");
45
+
46
+ -- CreateIndex
47
+ CREATE UNIQUE INDEX "team_invitations_token_key" ON "team_invitations"("token");
48
+
49
+ -- CreateIndex
50
+ CREATE INDEX "team_invitations_ownerId_idx" ON "team_invitations"("ownerId");
51
+
52
+ -- CreateIndex
53
+ CREATE INDEX "team_invitations_email_idx" ON "team_invitations"("email");
54
+
55
+ -- CreateIndex
56
+ CREATE UNIQUE INDEX "team_invitations_ownerId_email_key" ON "team_invitations"("ownerId", "email");
57
+
58
+ -- AddForeignKey
59
+ ALTER TABLE "team_memberships" ADD CONSTRAINT "team_memberships_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
60
+
61
+ -- AddForeignKey
62
+ ALTER TABLE "team_memberships" ADD CONSTRAINT "team_memberships_memberId_fkey" FOREIGN KEY ("memberId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
63
+
64
+ -- AddForeignKey
65
+ ALTER TABLE "team_invitations" ADD CONSTRAINT "team_invitations_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (e.g., Git)
3
+ provider = "postgresql"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allbluecn/database",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "license": "AGPL-3.0-or-later",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -15,7 +15,7 @@
15
15
  "pg": "^8.22.0",
16
16
  "sqlite-vec": "^0.1.9",
17
17
  "bcryptjs": "^3.0.2",
18
- "@allbluecn/shared": "0.4.4"
18
+ "@allbluecn/shared": "0.4.6"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/better-sqlite3": "^7.6.13",
@@ -41,8 +41,13 @@
41
41
  "!src/**/__tests__/**",
42
42
  "generated",
43
43
  "generated-sqlite",
44
+ "schema.prisma",
45
+ "schema.sqlite.prisma",
46
+ "prisma.config.ts",
47
+ "migrations",
44
48
  "desktop-init.ts",
45
- "desktop-init.d.ts"
49
+ "desktop-init.d.ts",
50
+ "index.ts"
46
51
  ],
47
52
  "scripts": {
48
53
  "generate": "prisma generate",
@@ -0,0 +1,18 @@
1
+ import "dotenv/config";
2
+ import { defineConfig, env } from "prisma/config";
3
+
4
+ export default defineConfig({
5
+ schema: "schema.prisma",
6
+ migrations: {
7
+ path: "migrations",
8
+ seed: "tsx seed.ts",
9
+ },
10
+ datasource: {
11
+ url: env("DATABASE_URL"),
12
+ // shadowDatabaseUrl 仅本地 `migrate dev` 需要(生成迁移时做 diff/reset)。
13
+ // 生产 `migrate deploy` 完全不读此字段,但 Prisma 7 config 加载阶段会全量求值,
14
+ // 用严格 env() 会在 Vercel 等未配置该变量的环境抛 PrismaConfigEnvError。
15
+ // 改用 process.env 兜底,缺失时为 undefined,赋给可选字段合法。
16
+ shadowDatabaseUrl: process.env.SHADOW_DATABASE_URL || undefined,
17
+ },
18
+ });