From 095f02700f915c5c3d59136ef0d9c3c215a8dbad Mon Sep 17 00:00:00 2001 From: karlcarlo Date: Thu, 29 Jan 2026 02:41:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E8=BF=81=E7=A7=BB=E8=84=9A=E6=9C=AC=E5=92=8C=20Supaba?= =?UTF-8?q?se=20=E5=B7=A5=E5=85=B7=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加迁移脚本以支持数据库版本管理,配置 pnpm workspace 仅构建 supabase 依赖 新增用户登录字段和任务帮助请求功能,禁用 tasks 和 task_steps 表的 RLS 更新环境变量示例以支持自托管 Supabase 实例 --- .env.sample | 10 +- package.json | 5 + pnpm-lock.yaml | 187 ++++++++++++++++++ pnpm-workspace.yaml | 2 + scripts/migrate.js | 148 ++++++++++++++ supabase/.temp/cli-latest | 1 + supabase/init_rpc.sql | 9 + .../20260126153000_create_tasks_tables.sql | 4 +- ...128000001_disable_rls_tasks_and_steps.sql} | 0 ...0260128000002_add_help_request_fields.sql} | 0 ... 20260128000003_add_user_login_fields.sql} | 0 11 files changed, 362 insertions(+), 4 deletions(-) create mode 100644 pnpm-workspace.yaml create mode 100644 scripts/migrate.js create mode 100644 supabase/.temp/cli-latest create mode 100644 supabase/init_rpc.sql rename supabase/migrations/{disable_rls_tasks_and_steps.sql => 20260128000001_disable_rls_tasks_and_steps.sql} (100%) rename supabase/migrations/{add_help_request_fields.sql => 20260128000002_add_help_request_fields.sql} (100%) rename supabase/migrations/{add_user_login_fields.sql => 20260128000003_add_user_login_fields.sql} (100%) diff --git a/.env.sample b/.env.sample index 7c399f6..1a61688 100644 --- a/.env.sample +++ b/.env.sample @@ -1,2 +1,8 @@ -NEXT_PUBLIC_SUPABASE_URL=https://vmtosvsveuopmqruxrpl.supabase.co -NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZtdG9zdnN2ZXVvcG1xcnV4cnBsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkzMTUzOTQsImV4cCI6MjA4NDg5MTM5NH0.KsVxObf4xRnkEZegjUgiRyWgA9cwTuLSAR9gOebBryQ +# SUPABASE +# NEXT_PUBLIC_SUPABASE_URL=https://vmtosvsveuopmqruxrpl.supabase.co +# NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZtdG9zdnN2ZXVvcG1xcnV4cnBsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkzMTUzOTQsImV4cCI6MjA4NDg5MTM5NH0.KsVxObf4xRnkEZegjUgiRyWgA9cwTuLSAR9gOebBryQ + +# ALIYUN SUPABASE +NEXT_PUBLIC_SUPABASE_URL=http://8.161.175.98 +NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJhbm9uIiwiaWF0IjoxNzY5NjA3MTMwLCJleHAiOjEzMjgwMjQ3MTMwfQ.HYbEkBJhf8YeLuNTEtO44aBApUuEkvXmxvEWIA1gUTQ +SUPABASE_SERVICE_ROLE_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJzZXJ2aWNlX3JvbGUiLCJpYXQiOjE3Njk2MDcxMzAsImV4cCI6MTMyODAyNDcxMzB9.8nGI1fVpeCCDGZJ84Hmd1mHgPwpEKB8q6PDNp6Z3ZEM \ No newline at end of file diff --git a/package.json b/package.json index 8364526..8a1e1a3 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "build": "next build", "start": "next start", "lint": "next lint", + "migrate": "node scripts/migrate.js", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -70,5 +71,9 @@ "typescript": "5.2.2", "vaul": "^0.9.9", "zod": "^3.23.8" + }, + "devDependencies": { + "dotenv": "^17.2.3", + "supabase": "^2.72.9" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d7c01df..debcf36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -188,6 +188,13 @@ importers: zod: specifier: ^3.23.8 version: 3.25.76 + devDependencies: + dotenv: + specifier: ^17.2.3 + version: 17.2.3 + supabase: + specifier: ^2.72.9 + version: 2.72.9 packages: @@ -259,6 +266,10 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1249,6 +1260,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1350,6 +1365,10 @@ packages: resolution: {integrity: sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==} hasBin: true + bin-links@6.0.0: + resolution: {integrity: sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w==} + engines: {node: ^20.17.0 || >=22.9.0} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1404,6 +1423,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1414,6 +1437,10 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + cmd-shim@8.0.0: + resolution: {integrity: sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==} + engines: {node: ^20.17.0 || >=22.9.0} + cmdk@1.1.1: resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} peerDependencies: @@ -1493,6 +1520,10 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -1563,6 +1594,10 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1766,6 +1801,10 @@ packages: picomatch: optional: true + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1789,6 +1828,10 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -1901,6 +1944,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + iceberg-js@0.8.1: resolution: {integrity: sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==} engines: {node: '>=20.0.0'} @@ -2153,6 +2200,14 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2193,6 +2248,15 @@ packages: sass: optional: true + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -2204,6 +2268,10 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + npm-normalize-package-bin@5.0.0: + resolution: {integrity: sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==} + engines: {node: ^20.17.0 || >=22.9.0} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2354,6 +2422,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + proc-log@6.1.0: + resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} + engines: {node: ^20.17.0 || >=22.9.0} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2442,6 +2514,10 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + read-cmd-shim@6.0.0: + resolution: {integrity: sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==} + engines: {node: ^20.17.0 || >=22.9.0} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2552,6 +2628,10 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -2630,6 +2710,11 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + supabase@2.72.9: + resolution: {integrity: sha512-kifjjxhjs0K1c10YiXh3bZ2lRp9s+MnloT1sTUAoV7mIkkfYn6XCq59hEYoNxfaZyE4j8nuQKszHz5XCMmFt9w==} + engines: {npm: '>=8'} + hasBin: true + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2651,6 +2736,10 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + tar@7.5.7: + resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} + engines: {node: '>=18'} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -2773,6 +2862,10 @@ packages: resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} engines: {node: '>=10.13.0'} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -2801,6 +2894,10 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + write-file-atomic@7.0.0: + resolution: {integrity: sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==} + engines: {node: ^20.17.0 || >=22.9.0} + ws@8.19.0: resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} engines: {node: '>=10.0.0'} @@ -2813,6 +2910,10 @@ packages: utf-8-validate: optional: true + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} @@ -2906,6 +3007,10 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -3877,6 +3982,8 @@ snapshots: acorn@8.15.0: {} + agent-base@7.1.4: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -4002,6 +4109,14 @@ snapshots: baseline-browser-mapping@2.9.18: {} + bin-links@6.0.0: + dependencies: + cmd-shim: 8.0.0 + npm-normalize-package-bin: 5.0.0 + proc-log: 6.1.0 + read-cmd-shim: 6.0.0 + write-file-atomic: 7.0.0 + binary-extensions@2.3.0: {} brace-expansion@1.1.12: @@ -4069,6 +4184,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chownr@3.0.0: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -4077,6 +4194,8 @@ snapshots: clsx@2.1.1: {} + cmd-shim@8.0.0: {} + cmdk@1.1.1(@types/react-dom@18.2.7)(@types/react@18.2.22)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.2.22)(react@18.2.0) @@ -4149,6 +4268,8 @@ snapshots: damerau-levenshtein@1.0.8: {} + data-uri-to-buffer@4.0.1: {} + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -4216,6 +4337,8 @@ snapshots: '@babel/runtime': 7.28.6 csstype: 3.2.3 + dotenv@17.2.3: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4563,6 +4686,11 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -4588,6 +4716,10 @@ snapshots: dependencies: is-callable: 1.2.7 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + fraction.js@4.3.7: {} fs.realpath@1.0.0: {} @@ -4714,6 +4846,13 @@ snapshots: dependencies: function-bind: 1.1.2 + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + iceberg-js@0.8.1: {} ignore@5.3.2: {} @@ -4957,6 +5096,12 @@ snapshots: minimist@1.2.8: {} + minipass@7.1.2: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.2 + ms@2.1.3: {} mz@2.7.0: @@ -5002,12 +5147,22 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-releases@2.0.27: {} normalize-path@3.0.0: {} normalize-range@0.1.2: {} + npm-normalize-package-bin@5.0.0: {} + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -5150,6 +5305,8 @@ snapshots: prelude-ls@1.2.1: {} + proc-log@6.1.0: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -5236,6 +5393,8 @@ snapshots: dependencies: pify: 2.3.0 + read-cmd-shim@6.0.0: {} + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -5386,6 +5545,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + signal-exit@4.1.0: {} + slash@3.0.0: {} sonner@1.7.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0): @@ -5477,6 +5638,15 @@ snapshots: tinyglobby: 0.2.15 ts-interface-checker: 0.1.13 + supabase@2.72.9: + dependencies: + bin-links: 6.0.0 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + tar: 7.5.7 + transitivePeerDependencies: + - supports-color + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -5516,6 +5686,14 @@ snapshots: transitivePeerDependencies: - ts-node + tar@7.5.7: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 + text-table@0.2.0: {} thenify-all@1.6.0: @@ -5686,6 +5864,8 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + web-streams-polyfill@3.3.3: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -5735,8 +5915,15 @@ snapshots: wrappy@1.0.2: {} + write-file-atomic@7.0.0: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + ws@8.19.0: {} + yallist@5.0.0: {} + yaml@2.8.2: {} yocto-queue@0.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..3e453e0 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +onlyBuiltDependencies: + - supabase diff --git a/scripts/migrate.js b/scripts/migrate.js new file mode 100644 index 0000000..53b2c99 --- /dev/null +++ b/scripts/migrate.js @@ -0,0 +1,148 @@ +const { createClient } = require('@supabase/supabase-js'); +const fs = require('fs'); +const path = require('path'); +const dotenv = require('dotenv'); + +// 加载环境变量 +dotenv.config({ path: '.env.local' }); +dotenv.config(); + +const MIGRATIONS_DIR = path.join(process.cwd(), 'supabase', 'migrations'); + +async function migrate() { + const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; + const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY; + + if (!supabaseUrl || !supabaseServiceKey) { + console.error('❌ 错误: 缺少必要的环境变量。'); + console.error('请确保 .env.local 中包含以下变量:'); + console.error(' - NEXT_PUBLIC_SUPABASE_URL'); + console.error(' - SUPABASE_SERVICE_ROLE_KEY (需要从 Supabase Dashboard 获取)'); + process.exit(1); + } + + // 使用 Service Role Key 初始化 Supabase 客户端,以获得执行 DDL 的权限 + const supabase = createClient(supabaseUrl, supabaseServiceKey, { + auth: { + autoRefreshToken: false, + persistSession: false, + }, + }); + + // 封装 SQL 执行函数,通过 RPC 调用 + const execSql = async (sql, params = []) => { + // 注意:这需要数据库中预先存在一个名为 'exec_sql' 的 RPC 函数 + // create or replace function exec_sql(sql text) returns void as $$ + // begin + // execute sql; + // end; + // $$ language plpgsql security definer; + + // 由于 exec_sql RPC 仅接受 sql 文本,我们这里简单地替换参数(如果只是简单的值替换) + // 或者如果 RPC 支持参数化查询则更好。目前的简单实现假设 SQL 已经完整。 + // 如果需要参数支持,建议直接拼接(仅限迁移脚本可控环境)或改进 RPC。 + // 这里的实现假设传入的 SQL 是完整的脚本。 + + const { error } = await supabase.rpc('exec_sql', { sql }); + if (error) { + throw new Error(error.message); + } + }; + + // 检查 RPC 是否可用 + try { + await execSql('SELECT 1'); + console.log('✅ 已连接到 Supabase (RPC exec_sql 可用)'); + } catch (err) { + console.error('❌ 连接失败或 exec_sql 函数不存在。'); + console.error('请确保您已在 Supabase 数据库中创建了 exec_sql 函数。'); + console.error('请查看 supabase/init_rpc.sql 获取创建脚本。'); + console.error('错误信息:', err.message); + process.exit(1); + } + + try { + // 确保 uuid-ossp 扩展已启用 (虽然我们现在主要使用 gen_random_uuid,但保留此行以兼容旧代码) + await execSql('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";'); + + // 创建迁移记录表 + await execSql(` + CREATE TABLE IF NOT EXISTS _migrations ( + id SERIAL PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + applied_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + ); + + -- 允许 Service Role 访问此表(虽然 Service Role 默认有权限,但明确授权更稳妥) + -- 或者简单地禁用此表的 RLS,因为它只在后端使用 + ALTER TABLE _migrations ENABLE ROW LEVEL SECURITY; + + -- 创建策略,允许所有操作(因为我们只通过 Service Role 访问) + -- 注意:如果策略已存在会报错,所以先尝试删除 + DROP POLICY IF EXISTS "Allow full access to service role" ON _migrations; + CREATE POLICY "Allow full access to service role" ON _migrations + USING (true) + WITH CHECK (true); + `); + + // 获取已执行的迁移 + // 由于 exec_sql 不返回值,我们需要通过标准 Supabase 查询来获取记录 + const { data: appliedMigrations, error: fetchError } = await supabase + .from('_migrations') + .select('name'); + + if (fetchError) { + throw new Error(`无法获取迁移记录: ${fetchError.message}`); + } + + const appliedNames = new Set(appliedMigrations.map((row) => row.name)); + + // 读取迁移文件 + const files = fs.readdirSync(MIGRATIONS_DIR) + .filter((file) => file.endsWith('.sql')) + .sort(); // 按文件名排序 + + let count = 0; + for (const file of files) { + if (appliedNames.has(file)) { + continue; + } + + console.log(`🚀 正在执行迁移: ${file}...`); + const filePath = path.join(MIGRATIONS_DIR, file); + const sql = fs.readFileSync(filePath, 'utf8'); + + try { + // 执行迁移 SQL + await execSql(sql); + + // 记录迁移状态 + const { error: insertError } = await supabase + .from('_migrations') + .insert({ name: file }); + + if (insertError) { + throw new Error(`无法记录迁移状态: ${insertError.message}`); + } + + console.log(`✅ 成功: ${file}`); + count++; + } catch (err) { + console.error(`❌ 失败: ${file}`); + console.error(err); + process.exit(1); + } + } + + if (count === 0) { + console.log('✨ 没有需要执行的迁移'); + } else { + console.log(`🎉 成功执行了 ${count} 个迁移`); + } + } catch (err) { + console.error('❌ 迁移过程中发生错误:', err); + process.exit(1); + } +} + +migrate(); diff --git a/supabase/.temp/cli-latest b/supabase/.temp/cli-latest new file mode 100644 index 0000000..9b07af9 --- /dev/null +++ b/supabase/.temp/cli-latest @@ -0,0 +1 @@ +v2.72.7 \ No newline at end of file diff --git a/supabase/init_rpc.sql b/supabase/init_rpc.sql new file mode 100644 index 0000000..7c972d1 --- /dev/null +++ b/supabase/init_rpc.sql @@ -0,0 +1,9 @@ +-- 这是一个初始化脚本,用于在 Supabase 中启用 SQL 执行功能。 +-- 请复制以下内容并在 Supabase Dashboard 的 SQL 编辑器中运行一次。 +-- 运行后,scripts/migrate.js 脚本才能通过 RPC 调用执行迁移。 + +create or replace function exec_sql(sql text) returns void as $$ +begin + execute sql; +end; +$$ language plpgsql security definer; diff --git a/supabase/migrations/20260126153000_create_tasks_tables.sql b/supabase/migrations/20260126153000_create_tasks_tables.sql index 61bf455..4d40d19 100644 --- a/supabase/migrations/20260126153000_create_tasks_tables.sql +++ b/supabase/migrations/20260126153000_create_tasks_tables.sql @@ -1,7 +1,7 @@ -- Create tasks table CREATE TABLE IF NOT EXISTS tasks ( - id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, kr_id UUID NOT NULL REFERENCES krs(id) ON DELETE CASCADE, title TEXT NOT NULL, description TEXT, -- 思路 @@ -18,7 +18,7 @@ CREATE TABLE IF NOT EXISTS tasks ( -- Create task_steps table CREATE TABLE IF NOT EXISTS task_steps ( - id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE, content TEXT NOT NULL, note TEXT, diff --git a/supabase/migrations/disable_rls_tasks_and_steps.sql b/supabase/migrations/20260128000001_disable_rls_tasks_and_steps.sql similarity index 100% rename from supabase/migrations/disable_rls_tasks_and_steps.sql rename to supabase/migrations/20260128000001_disable_rls_tasks_and_steps.sql diff --git a/supabase/migrations/add_help_request_fields.sql b/supabase/migrations/20260128000002_add_help_request_fields.sql similarity index 100% rename from supabase/migrations/add_help_request_fields.sql rename to supabase/migrations/20260128000002_add_help_request_fields.sql diff --git a/supabase/migrations/add_user_login_fields.sql b/supabase/migrations/20260128000003_add_user_login_fields.sql similarity index 100% rename from supabase/migrations/add_user_login_fields.sql rename to supabase/migrations/20260128000003_add_user_login_fields.sql