chore: 优化本地开发环境配置
- 添加 .env.local 支持,app.sh 和 dev-migrate.sh 自动覆盖 - Docker Compose 使用 profiles 区分 dev/prod 环境 - 改进认证 dev session 判断逻辑,使用 test account 配置 - 修复 CoinPackageCard 重复代码问题 - 清理 opencode 配置,移除敏感信息 - 新增 infra/docker/README.md 文档 - 修复 ruff/pyright/flutter lint 错误 - 更新测试用例移除已删除的 country 字段
This commit is contained in:
@@ -306,6 +306,7 @@ infra/docker/supabase/volumes/storage/
|
|||||||
|
|
||||||
# OpenCode local config
|
# OpenCode local config
|
||||||
# .opencode/ is now tracked - see .opencode/.gitignore for exclusions
|
# .opencode/ is now tracked - see .opencode/.gitignore for exclusions
|
||||||
|
.opencode/opencode.json
|
||||||
midscene_run/
|
midscene_run/
|
||||||
|
|
||||||
# Local git worktrees
|
# Local git worktrees
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"supabase": {
|
"supabase": {
|
||||||
"type": "remote",
|
"type": "remote",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"url": "http://localhost:8001/mcp"
|
"url": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://opencode.ai/config.json",
|
|
||||||
"mcp": {
|
|
||||||
"supabase": {
|
|
||||||
"type": "local",
|
|
||||||
"enabled": true,
|
|
||||||
"command": [
|
|
||||||
"npx",
|
|
||||||
"-y",
|
|
||||||
"@aliyun-rds/supabase-mcp-server",
|
|
||||||
"--supabase-url",
|
|
||||||
"http://47.112.66.83",
|
|
||||||
"--supabase-anon-key",
|
|
||||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJhbm9uIiwiaWF0IjoxNzczMDI3NDE5LCJleHAiOjEzMjgzNjY3NDE5fQ.NVXDla5_nYPdcJk_81fc3k1UrnNTrNne_trMqt6Hg4g",
|
|
||||||
"--supabase-service-role-key",
|
|
||||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJzZXJ2aWNlX3JvbGUiLCJpYXQiOjE3NzMwMjc0MTksImV4cCI6MTMyODM2Njc0MTl9.RzQBia-3QcjupsHnqaxgDWB7wnY9R7Ms9R8pMokyvLY"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
+376
@@ -0,0 +1,376 @@
|
|||||||
|
{
|
||||||
|
"name": ".opencode",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"@opencode-ai/plugin": "1.14.22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@opencode-ai/plugin": {
|
||||||
|
"version": "1.14.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.14.22.tgz",
|
||||||
|
"integrity": "sha512-lJlukegf5ECEHm9Y0NxCjXNfUArpPSUHP6hc+M4VCJ3NFk8uzzVsIXAzPS9Hvf2ltzjEYD/ulCOTi6pleeZ6yw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@opencode-ai/sdk": "1.14.22",
|
||||||
|
"effect": "4.0.0-beta.48",
|
||||||
|
"zod": "4.1.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@opentui/core": ">=0.1.99",
|
||||||
|
"@opentui/solid": ">=0.1.99"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@opentui/core": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@opentui/solid": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@opencode-ai/sdk": {
|
||||||
|
"version": "1.14.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.14.22.tgz",
|
||||||
|
"integrity": "sha512-1PjkrZRAwm9ocfTwOleP/e31HYtLVODb2E1hYTRHMmvF2rmAdCm7lztguYVkAPn/B6koGpFvhslTQH7j+38Fjw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-spawn": "7.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@standard-schema/spec": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/cross-spawn": {
|
||||||
|
"version": "7.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
|
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"path-key": "^3.1.0",
|
||||||
|
"shebang-command": "^2.0.0",
|
||||||
|
"which": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/detect-libc": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/effect": {
|
||||||
|
"version": "4.0.0-beta.48",
|
||||||
|
"resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.48.tgz",
|
||||||
|
"integrity": "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/spec": "^1.1.0",
|
||||||
|
"fast-check": "^4.6.0",
|
||||||
|
"find-my-way-ts": "^0.1.6",
|
||||||
|
"ini": "^6.0.0",
|
||||||
|
"kubernetes-types": "^1.30.0",
|
||||||
|
"msgpackr": "^1.11.9",
|
||||||
|
"multipasta": "^0.2.7",
|
||||||
|
"toml": "^4.1.1",
|
||||||
|
"uuid": "^13.0.0",
|
||||||
|
"yaml": "^2.8.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fast-check": {
|
||||||
|
"version": "4.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz",
|
||||||
|
"integrity": "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/dubzzz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fast-check"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pure-rand": "^8.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.17.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/find-my-way-ts": {
|
||||||
|
"version": "0.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz",
|
||||||
|
"integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/ini": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.17.0 || >=22.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/isexe": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/kubernetes-types": {
|
||||||
|
"version": "1.30.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/kubernetes-types/-/kubernetes-types-1.30.0.tgz",
|
||||||
|
"integrity": "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/msgpackr": {
|
||||||
|
"version": "1.11.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.10.tgz",
|
||||||
|
"integrity": "sha512-iCZNq+HszvF+fC3anCm4nBmWEnbeIAfpDs6IStAEKhQ2YSgkjzVG2FF9XJqwwQh5bH3N9OUTUt4QwVN6MLMLtA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"msgpackr-extract": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/msgpackr-extract": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"node-gyp-build-optional-packages": "5.2.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"download-msgpackr-prebuilds": "bin/download-prebuilds.js"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/multipasta": {
|
||||||
|
"version": "0.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.7.tgz",
|
||||||
|
"integrity": "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/node-gyp-build-optional-packages": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"node-gyp-build-optional-packages": "bin.js",
|
||||||
|
"node-gyp-build-optional-packages-optional": "optional.js",
|
||||||
|
"node-gyp-build-optional-packages-test": "build-test.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-key": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pure-rand": {
|
||||||
|
"version": "8.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz",
|
||||||
|
"integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/dubzzz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fast-check"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/shebang-command": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"shebang-regex": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/shebang-regex": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/toml": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/toml/-/toml-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "13.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
|
||||||
|
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist-node/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/which": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"isexe": "^2.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"node-which": "bin/node-which"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
|
||||||
|
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/eemeli"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "4.1.8",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,7 +108,7 @@ class _CoinCenterScreenState extends State<CoinCenterScreen> {
|
|||||||
_packages = result.packages;
|
_packages = result.packages;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e, stackTrace) {
|
} catch (e) {
|
||||||
_logger.warning(
|
_logger.warning(
|
||||||
message: 'Failed to reload packages after purchase',
|
message: 'Failed to reload packages after purchase',
|
||||||
extra: {'error': e.toString()},
|
extra: {'error': e.toString()},
|
||||||
|
|||||||
@@ -445,74 +445,6 @@ class CoinPackageCard extends StatelessWidget {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||||
),
|
),
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: AppSpacing.xs),
|
|
||||||
Text(
|
|
||||||
l10n.settingsCoinAmount(amount),
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (badge != null)
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: AppSpacing.sm,
|
|
||||||
vertical: AppSpacing.xs,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: palette.historyGoldBg,
|
|
||||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
badge!,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: palette.historyGoldText,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: AppSpacing.lg),
|
|
||||||
if (!isAvailable && unavailableMessage != null)
|
|
||||||
Text(
|
|
||||||
unavailableMessage!,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: colors.error,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
price,
|
|
||||||
style: Theme.of(
|
|
||||||
context,
|
|
||||||
).textTheme.headlineMedium?.copyWith(color: colors.primary),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: isPurchasing || !isAvailable ? null : onPurchase,
|
|
||||||
style: FilledButton.styleFrom(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ This file governs `backend/**` only. Keep it minimal, enforceable, and non-dupli
|
|||||||
- Python commands must use `uv` (`uv run`, `uv add`).
|
- Python commands must use `uv` (`uv run`, `uv add`).
|
||||||
- Backend startup/shutdown must use `./infra/scripts/app.sh`.
|
- Backend startup/shutdown must use `./infra/scripts/app.sh`.
|
||||||
- Check runtime logs from `./logs/*.log`.
|
- Check runtime logs from `./logs/*.log`.
|
||||||
|
- Docker Compose usage: see `infra/docker/README.md` for environment-based service activation.
|
||||||
|
|
||||||
## Code Quality Baseline
|
## Code Quality Baseline
|
||||||
|
|
||||||
|
|||||||
@@ -189,8 +189,8 @@ class SensitiveWordSettings(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class TestSettings(BaseModel):
|
class TestSettings(BaseModel):
|
||||||
phone: str = ""
|
email: str = ""
|
||||||
password: str = ""
|
code: str = ""
|
||||||
|
|
||||||
|
|
||||||
class TaskiqSettings(BaseModel):
|
class TaskiqSettings(BaseModel):
|
||||||
|
|||||||
@@ -78,7 +78,10 @@ class SupabaseAuthGateway(AuthServiceGateway):
|
|||||||
async def create_email_session(
|
async def create_email_session(
|
||||||
self, request: EmailSessionCreateRequest
|
self, request: EmailSessionCreateRequest
|
||||||
) -> SessionResponse:
|
) -> SessionResponse:
|
||||||
if config.runtime.environment == "dev":
|
test_email = config.test.email.strip()
|
||||||
|
test_code = config.test.code.strip()
|
||||||
|
if test_email and test_code:
|
||||||
|
if request.email == test_email and request.token == test_code:
|
||||||
return await create_dev_email_session(
|
return await create_dev_email_session(
|
||||||
request=request,
|
request=request,
|
||||||
client=self._get_client(),
|
client=self._get_client(),
|
||||||
|
|||||||
@@ -434,19 +434,16 @@ class PaymentService:
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import jwt as pyjwt
|
|
||||||
|
|
||||||
parts = signed_payload.split(".")
|
parts = signed_payload.split(".")
|
||||||
if len(parts) < 2:
|
if len(parts) < 2:
|
||||||
logger.warning("Malformed Apple notification signed_payload")
|
logger.warning("Malformed Apple notification signed_payload")
|
||||||
return
|
return
|
||||||
|
|
||||||
payload_bytes = parts[1] + "=" * (-len(parts[1]) % 4)
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
decoded = base64.urlsafe_b64decode(payload_bytes)
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
payload_bytes = parts[1] + "=" * (-len(parts[1]) % 4)
|
||||||
|
decoded = base64.urlsafe_b64decode(payload_bytes)
|
||||||
notification_data: Any = json.loads(decoded)
|
notification_data: Any = json.loads(decoded)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Failed to decode Apple server notification payload")
|
logger.exception("Failed to decode Apple server notification payload")
|
||||||
|
|||||||
@@ -500,9 +500,9 @@ class PointsService:
|
|||||||
id=str(row.id),
|
id=str(row.id),
|
||||||
direction=row.direction,
|
direction=row.direction,
|
||||||
amount=row.amount,
|
amount=row.amount,
|
||||||
balance_after=row.balance_after,
|
balanceAfter=row.balance_after,
|
||||||
change_type=row.change_type,
|
changeType=row.change_type,
|
||||||
created_at=row.created_at.isoformat(),
|
createdAt=row.created_at.isoformat(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Integration tests for Apple IAP payment verify flow.
|
Integration tests for Apple IAP payment verify flow.
|
||||||
|
|
||||||
@@ -7,6 +5,8 @@ Prerequisite: backend must be running via `./infra/scripts/app.sh restart`.
|
|||||||
These tests hit the live HTTP API against the test database.
|
These tests hit the live HTTP API against the test database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import json
|
|||||||
from v1.payments.apple_verifier import (
|
from v1.payments.apple_verifier import (
|
||||||
AppleJwsVerifier,
|
AppleJwsVerifier,
|
||||||
VerificationError,
|
VerificationError,
|
||||||
VerifiedTransaction,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -571,7 +571,6 @@ class TestHandleServerNotificationRefund:
|
|||||||
verifier=_FakeVerifier(result=_make_verified_transaction()),
|
verifier=_FakeVerifier(result=_make_verified_transaction()),
|
||||||
)
|
)
|
||||||
|
|
||||||
import base64
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
signed_txn = _make_fake_signed_transaction(transaction_id="2000000999999001")
|
signed_txn = _make_fake_signed_transaction(transaction_id="2000000999999001")
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from v1.auth.schemas import AuthUser, EmailSessionCreateRequest, SessionResponse
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_create_email_session_uses_dev_bypass(
|
async def test_create_email_session_uses_test_account_bypass(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
) -> None:
|
) -> None:
|
||||||
gateway = SupabaseAuthGateway()
|
gateway = SupabaseAuthGateway()
|
||||||
@@ -28,7 +28,8 @@ async def test_create_email_session_uses_dev_bypass(
|
|||||||
calls.update(kwargs)
|
calls.update(kwargs)
|
||||||
return expected
|
return expected
|
||||||
|
|
||||||
monkeypatch.setattr(gateway_module.config.runtime, "environment", "dev")
|
monkeypatch.setattr(gateway_module.config.test, "email", "test@example.com")
|
||||||
|
monkeypatch.setattr(gateway_module.config.test, "code", "123456")
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
gateway_module, "create_dev_email_session", _fake_create_dev_email_session
|
gateway_module, "create_dev_email_session", _fake_create_dev_email_session
|
||||||
)
|
)
|
||||||
@@ -47,7 +48,7 @@ async def test_create_email_session_uses_dev_bypass(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_create_email_session_uses_verify_otp_in_non_dev(
|
async def test_create_email_session_uses_verify_otp_when_test_account_not_configured(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
) -> None:
|
) -> None:
|
||||||
gateway = SupabaseAuthGateway()
|
gateway = SupabaseAuthGateway()
|
||||||
@@ -66,7 +67,8 @@ async def test_create_email_session_uses_verify_otp_in_non_dev(
|
|||||||
user=SimpleNamespace(id="user-2", email="test@example.com"),
|
user=SimpleNamespace(id="user-2", email="test@example.com"),
|
||||||
)
|
)
|
||||||
|
|
||||||
monkeypatch.setattr(gateway_module.config.runtime, "environment", "prod")
|
monkeypatch.setattr(gateway_module.config.test, "email", "")
|
||||||
|
monkeypatch.setattr(gateway_module.config.test, "code", "")
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
gateway,
|
gateway,
|
||||||
"_get_client",
|
"_get_client",
|
||||||
@@ -81,3 +83,79 @@ async def test_create_email_session_uses_verify_otp_in_non_dev(
|
|||||||
"token": "123456",
|
"token": "123456",
|
||||||
}
|
}
|
||||||
assert response.user.email == "test@example.com"
|
assert response.user.email == "test@example.com"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_email_session_uses_verify_otp_when_email_mismatch(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
gateway = SupabaseAuthGateway()
|
||||||
|
request = EmailSessionCreateRequest(email="other@example.com", token="123456")
|
||||||
|
captured_payload: dict[str, str] = {}
|
||||||
|
|
||||||
|
def _verify_otp(payload: dict[str, str]) -> SimpleNamespace:
|
||||||
|
captured_payload.update(payload)
|
||||||
|
return SimpleNamespace(
|
||||||
|
session=SimpleNamespace(
|
||||||
|
access_token="access",
|
||||||
|
refresh_token="refresh",
|
||||||
|
expires_in=3600,
|
||||||
|
token_type="bearer",
|
||||||
|
),
|
||||||
|
user=SimpleNamespace(id="user-3", email="other@example.com"),
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr(gateway_module.config.test, "email", "test@example.com")
|
||||||
|
monkeypatch.setattr(gateway_module.config.test, "code", "123456")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
gateway,
|
||||||
|
"_get_client",
|
||||||
|
lambda: SimpleNamespace(auth=SimpleNamespace(verify_otp=_verify_otp)),
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await gateway.create_email_session(request)
|
||||||
|
|
||||||
|
assert captured_payload == {
|
||||||
|
"type": "email",
|
||||||
|
"email": "other@example.com",
|
||||||
|
"token": "123456",
|
||||||
|
}
|
||||||
|
assert response.user.email == "other@example.com"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_email_session_uses_verify_otp_when_code_mismatch(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
gateway = SupabaseAuthGateway()
|
||||||
|
request = EmailSessionCreateRequest(email="test@example.com", token="654321")
|
||||||
|
captured_payload: dict[str, str] = {}
|
||||||
|
|
||||||
|
def _verify_otp(payload: dict[str, str]) -> SimpleNamespace:
|
||||||
|
captured_payload.update(payload)
|
||||||
|
return SimpleNamespace(
|
||||||
|
session=SimpleNamespace(
|
||||||
|
access_token="access",
|
||||||
|
refresh_token="refresh",
|
||||||
|
expires_in=3600,
|
||||||
|
token_type="bearer",
|
||||||
|
),
|
||||||
|
user=SimpleNamespace(id="user-4", email="test@example.com"),
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr(gateway_module.config.test, "email", "test@example.com")
|
||||||
|
monkeypatch.setattr(gateway_module.config.test, "code", "123456")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
gateway,
|
||||||
|
"_get_client",
|
||||||
|
lambda: SimpleNamespace(auth=SimpleNamespace(verify_otp=_verify_otp)),
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await gateway.create_email_session(request)
|
||||||
|
|
||||||
|
assert captured_payload == {
|
||||||
|
"type": "email",
|
||||||
|
"email": "test@example.com",
|
||||||
|
"token": "654321",
|
||||||
|
}
|
||||||
|
assert response.user.email == "test@example.com"
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ class TestParseProfileSettings:
|
|||||||
"preferences": {
|
"preferences": {
|
||||||
"language": "en-US",
|
"language": "en-US",
|
||||||
"timezone": "America/New_York",
|
"timezone": "America/New_York",
|
||||||
"country": "US",
|
|
||||||
},
|
},
|
||||||
"privacy": {"profile_visibility": "private"},
|
"privacy": {"profile_visibility": "private"},
|
||||||
"notification": {
|
"notification": {
|
||||||
@@ -32,7 +31,6 @@ class TestParseProfileSettings:
|
|||||||
assert isinstance(result.preferences, PreferenceSettings)
|
assert isinstance(result.preferences, PreferenceSettings)
|
||||||
assert result.preferences.language == "en-US"
|
assert result.preferences.language == "en-US"
|
||||||
assert result.preferences.timezone == "America/New_York"
|
assert result.preferences.timezone == "America/New_York"
|
||||||
assert result.preferences.country == "US"
|
|
||||||
assert isinstance(result.notification, NotificationSettings)
|
assert isinstance(result.notification, NotificationSettings)
|
||||||
assert result.notification.allow_notifications is True
|
assert result.notification.allow_notifications is True
|
||||||
assert result.notification.allow_vibration is False
|
assert result.notification.allow_vibration is False
|
||||||
@@ -45,7 +43,6 @@ class TestParseProfileSettings:
|
|||||||
assert isinstance(result.preferences, PreferenceSettings)
|
assert isinstance(result.preferences, PreferenceSettings)
|
||||||
assert result.preferences.language == "zh-CN"
|
assert result.preferences.language == "zh-CN"
|
||||||
assert result.preferences.timezone == "Asia/Shanghai"
|
assert result.preferences.timezone == "Asia/Shanghai"
|
||||||
assert result.preferences.country == "US"
|
|
||||||
assert isinstance(result.notification, NotificationSettings)
|
assert isinstance(result.notification, NotificationSettings)
|
||||||
assert result.notification.allow_notifications is True
|
assert result.notification.allow_notifications is True
|
||||||
assert result.notification.allow_vibration is True
|
assert result.notification.allow_vibration is True
|
||||||
@@ -60,7 +57,6 @@ class TestParseProfileSettings:
|
|||||||
|
|
||||||
assert result.preferences.language == "en-US"
|
assert result.preferences.language == "en-US"
|
||||||
assert result.preferences.timezone == "Asia/Shanghai"
|
assert result.preferences.timezone == "Asia/Shanghai"
|
||||||
assert result.preferences.country == "US"
|
|
||||||
|
|
||||||
def test_parse_profile_settings_with_partial_notification(self) -> None:
|
def test_parse_profile_settings_with_partial_notification(self) -> None:
|
||||||
raw = {
|
raw = {
|
||||||
@@ -104,21 +100,11 @@ class TestParseProfileSettings:
|
|||||||
with pytest.raises(ValueError, match="timezone must be a valid IANA timezone"):
|
with pytest.raises(ValueError, match="timezone must be a valid IANA timezone"):
|
||||||
parse_profile_settings(raw)
|
parse_profile_settings(raw)
|
||||||
|
|
||||||
def test_parse_profile_settings_country_normalized_to_uppercase(self) -> None:
|
|
||||||
raw = {
|
|
||||||
"preferences": {
|
|
||||||
"country": "us",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
result = parse_profile_settings(raw)
|
|
||||||
assert result.preferences.country == "US"
|
|
||||||
|
|
||||||
def test_profile_settings_v1_model_dump(self) -> None:
|
def test_profile_settings_v1_model_dump(self) -> None:
|
||||||
settings = ProfileSettingsV1(
|
settings = ProfileSettingsV1(
|
||||||
preferences=PreferenceSettings(
|
preferences=PreferenceSettings(
|
||||||
language="en-US",
|
language="en-US",
|
||||||
timezone="UTC",
|
timezone="UTC",
|
||||||
country="US",
|
|
||||||
),
|
),
|
||||||
notification=NotificationSettings(
|
notification=NotificationSettings(
|
||||||
allow_notifications=True,
|
allow_notifications=True,
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
# Docker Compose Infrastructure
|
||||||
|
|
||||||
|
> Local development infrastructure orchestration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This directory contains Docker Compose configurations for local development:
|
||||||
|
|
||||||
|
- `docker-compose.yml` - Base services (Redis, etc.)
|
||||||
|
- `supabase/docker-compose.yml` - Local Supabase stack (PostgreSQL, Auth, Storage, Studio, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment-Based Service Activation
|
||||||
|
|
||||||
|
Services are conditionally enabled based on `ERYAO_RUNTIME__ENVIRONMENT`:
|
||||||
|
|
||||||
|
| Environment | Services |
|
||||||
|
|-------------|----------|
|
||||||
|
| `dev` | Redis + Local Supabase |
|
||||||
|
| `prod` | Redis only (use cloud Supabase) |
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
Supabase services use `profiles: [dev]`. They only start when the `dev` profile is activated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Development (with local Supabase)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd infra/docker
|
||||||
|
docker compose --profile dev up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with environment variable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ERYAO_RUNTIME__ENVIRONMENT=dev docker compose --profile dev up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production (cloud Supabase)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd infra/docker
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Services Reference
|
||||||
|
|
||||||
|
### Base Services (always started)
|
||||||
|
|
||||||
|
| Service | Port | Description |
|
||||||
|
|---------|------|-------------|
|
||||||
|
| Redis | 6379 | Caching and queue backend |
|
||||||
|
|
||||||
|
### Dev-Only Services (profile: dev)
|
||||||
|
|
||||||
|
| Service | Port | Description |
|
||||||
|
|---------|------|-------------|
|
||||||
|
| PostgreSQL | 5432 | Local database |
|
||||||
|
| GoTrue (Auth) | 9999 | Authentication service |
|
||||||
|
| PostgREST | 3000 | REST API for database |
|
||||||
|
| Storage | 5000 | File storage service |
|
||||||
|
| Studio | 3000 | Supabase dashboard UI |
|
||||||
|
| Kong | 8001, 8443 | API gateway |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
All services read configuration from `.env` (symlinked to project root `.env`).
|
||||||
|
|
||||||
|
Required environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Runtime
|
||||||
|
ERYAO_RUNTIME__ENVIRONMENT=dev
|
||||||
|
|
||||||
|
# Database
|
||||||
|
ERYAO_DATABASE__PORT=5432
|
||||||
|
ERYAO_DATABASE__NAME=postgres
|
||||||
|
ERYAO_DATABASE__PASSWORD=your-password
|
||||||
|
|
||||||
|
# Supabase
|
||||||
|
ERYAO_SUPABASE__JWT_SECRET=your-jwt-secret
|
||||||
|
ERYAO_SUPABASE__ANON_KEY=your-anon-key
|
||||||
|
ERYAO_SUPABASE__SERVICE_ROLE_KEY=your-service-role-key
|
||||||
|
ERYAO_SUPABASE__PUBLIC_URL=http://localhost:8001
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
ERYAO_REDIS__PORT=6379
|
||||||
|
ERYAO_REDIS__PASSWORD=your-redis-password
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Supabase services not starting
|
||||||
|
|
||||||
|
1. Check profile is activated: `docker compose --profile dev config`
|
||||||
|
2. Verify environment variables in `.env`
|
||||||
|
3. Check logs: `docker compose logs <service-name>`
|
||||||
|
|
||||||
|
### Port conflicts
|
||||||
|
|
||||||
|
If ports are already in use, override in `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ERYAO_DATABASE__PORT=5433
|
||||||
|
ERYAO_REDIS__PORT=6380
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reset local database
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose --profile dev down -v
|
||||||
|
docker compose --profile dev up -d
|
||||||
|
```
|
||||||
@@ -22,7 +22,7 @@ services:
|
|||||||
"CMD",
|
"CMD",
|
||||||
"sh",
|
"sh",
|
||||||
"-c",
|
"-c",
|
||||||
"if [ -n \"$$REDIS_PASSWORD\" ]; then redis-cli -a \"$$REDIS_PASSWORD\" ping; else redis-cli ping; fi",
|
'if [ -n "$$REDIS_PASSWORD" ]; then redis-cli -a "$$REDIS_PASSWORD" ping; else redis-cli ping; fi',
|
||||||
]
|
]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ name: eryao-supabase
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
|
profiles: [dev]
|
||||||
container_name: eryao-supabase-db
|
container_name: eryao-supabase-db
|
||||||
image: supabase/postgres:15.8.1.085
|
image: supabase/postgres:15.8.1.085
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -33,6 +34,7 @@ services:
|
|||||||
- 127.0.0.1:${ERYAO_DATABASE__PORT:-5432}:5432
|
- 127.0.0.1:${ERYAO_DATABASE__PORT:-5432}:5432
|
||||||
|
|
||||||
auth:
|
auth:
|
||||||
|
profiles: [dev]
|
||||||
container_name: eryao-supabase-auth
|
container_name: eryao-supabase-auth
|
||||||
image: supabase/gotrue:v2.186.0
|
image: supabase/gotrue:v2.186.0
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -73,6 +75,7 @@ services:
|
|||||||
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: /auth/v1/verify
|
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: /auth/v1/verify
|
||||||
|
|
||||||
rest:
|
rest:
|
||||||
|
profiles: [dev]
|
||||||
container_name: eryao-supabase-rest
|
container_name: eryao-supabase-rest
|
||||||
image: postgrest/postgrest:v14.8
|
image: postgrest/postgrest:v14.8
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -91,6 +94,7 @@ services:
|
|||||||
PGRST_APP_SETTINGS_JWT_EXP: 3600
|
PGRST_APP_SETTINGS_JWT_EXP: 3600
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
|
profiles: [dev]
|
||||||
container_name: eryao-supabase-storage
|
container_name: eryao-supabase-storage
|
||||||
image: supabase/storage-api:v1.48.26
|
image: supabase/storage-api:v1.48.26
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -124,6 +128,7 @@ services:
|
|||||||
- storage-data:/var/lib/storage
|
- storage-data:/var/lib/storage
|
||||||
|
|
||||||
meta:
|
meta:
|
||||||
|
profiles: [dev]
|
||||||
container_name: eryao-supabase-meta
|
container_name: eryao-supabase-meta
|
||||||
image: supabase/postgres-meta:v0.96.3
|
image: supabase/postgres-meta:v0.96.3
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -145,6 +150,7 @@ services:
|
|||||||
retries: 1
|
retries: 1
|
||||||
|
|
||||||
studio:
|
studio:
|
||||||
|
profiles: [dev]
|
||||||
container_name: eryao-supabase-studio
|
container_name: eryao-supabase-studio
|
||||||
image: supabase/studio:2026.04.08-sha-205cbe7
|
image: supabase/studio:2026.04.08-sha-205cbe7
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -180,6 +186,7 @@ services:
|
|||||||
- studio-functions:/var/lib/functions
|
- studio-functions:/var/lib/functions
|
||||||
|
|
||||||
kong:
|
kong:
|
||||||
|
profiles: [dev]
|
||||||
container_name: eryao-supabase-kong
|
container_name: eryao-supabase-kong
|
||||||
image: kong/kong:3.9.1
|
image: kong/kong:3.9.1
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ load_env_if_exists() {
|
|||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
. "$ENV_LOADER"
|
. "$ENV_LOADER"
|
||||||
load_env_file "$ENV_FILE"
|
load_env_file "$ENV_FILE"
|
||||||
|
load_env_file "$ROOT_DIR/.env.local"
|
||||||
}
|
}
|
||||||
|
|
||||||
is_port_in_use() {
|
is_port_in_use() {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ fi
|
|||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
. "$ENV_LOADER"
|
. "$ENV_LOADER"
|
||||||
load_env_file "$ENV_FILE"
|
load_env_file "$ENV_FILE"
|
||||||
|
load_env_file "$ROOT_DIR/.env.local"
|
||||||
|
|
||||||
cd "$ROOT_DIR"
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user