@laitszkin/apollo-toolkit 3.10.0 → 3.11.0
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/CHANGELOG.md +20 -0
- package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
- package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
- package/generate-spec/SKILL.md +17 -15
- package/generate-spec/agents/openai.yaml +1 -1
- package/generate-spec/references/TEMPLATE_SPEC.md +103 -84
- package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
- package/init-project-html/SKILL.md +82 -126
- package/init-project-html/agents/openai.yaml +17 -8
- package/init-project-html/lib/atlas/assets/architecture.css +140 -0
- package/init-project-html/lib/atlas/assets/viewer.client.js +93 -0
- package/init-project-html/lib/atlas/cli.js +995 -0
- package/init-project-html/lib/atlas/layout.js +229 -0
- package/init-project-html/lib/atlas/render.js +485 -0
- package/init-project-html/lib/atlas/schema.js +310 -0
- package/init-project-html/lib/atlas/state.js +402 -0
- package/init-project-html/references/TEMPLATE_SPEC.md +123 -84
- package/init-project-html/sample-demo/resources/project-architecture/assets/architecture.css +139 -1058
- package/init-project-html/sample-demo/resources/project-architecture/assets/viewer.client.js +93 -0
- package/init-project-html/sample-demo/resources/project-architecture/atlas/atlas.index.yaml +34 -0
- package/init-project-html/sample-demo/resources/project-architecture/atlas/features/get-invite-codes.yaml +159 -0
- package/init-project-html/sample-demo/resources/project-architecture/atlas/features/invite-code-registration.yaml +160 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/index.html +67 -52
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-code-generator.html +48 -163
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-issuance-service.html +70 -196
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/postgresql.html +64 -163
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/public-api.html +68 -150
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/web-get-invite-ui.html +65 -138
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/index.html +61 -51
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/postgresql.html +66 -159
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/public-api.html +63 -143
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/registration-service.html +77 -188
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/web-register-ui.html +65 -138
- package/init-project-html/sample-demo/resources/project-architecture/index.html +232 -335
- package/init-project-html/scripts/architecture.js +65 -247
- package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
- package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
- package/package.json +6 -2
- package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
- package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
- package/spec-to-project-html/SKILL.md +61 -63
- package/spec-to-project-html/agents/openai.yaml +14 -8
- package/spec-to-project-html/references/TEMPLATE_SPEC.md +96 -83
- package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
|
@@ -1,140 +1,67 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<
|
|
14
|
-
</
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
</tr>
|
|
68
|
-
</tbody>
|
|
69
|
-
</table>
|
|
70
|
-
</section>
|
|
71
|
-
|
|
72
|
-
<section class="sub-vars">
|
|
73
|
-
<h2>變數與業務用途</h2>
|
|
74
|
-
<p class="sub-vars__intro">本子模組以前端組件狀態為主;下表為決定使用者體驗的識別子。</p>
|
|
75
|
-
<table>
|
|
76
|
-
<thead><tr><th>變數</th><th>型別</th><th>作用域</th><th>業務用途</th></tr></thead>
|
|
77
|
-
<tbody>
|
|
78
|
-
<tr>
|
|
79
|
-
<td class="sub-vars__name">list</td>
|
|
80
|
-
<td class="sub-vars__type">InviteCode[]</td>
|
|
81
|
-
<td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
|
|
82
|
-
<td>使用者已發放的邀請碼清單;驅動畫面呈現與「複製」按鈕可用性;保留順序讓使用者能識別最近一次取得的碼。</td>
|
|
83
|
-
</tr>
|
|
84
|
-
<tr>
|
|
85
|
-
<td class="sub-vars__name">isLoading</td>
|
|
86
|
-
<td class="sub-vars__type">boolean</td>
|
|
87
|
-
<td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
|
|
88
|
-
<td>生成 API 進行中旗標;防止連按重複生成造成額度被無意義消耗。</td>
|
|
89
|
-
</tr>
|
|
90
|
-
<tr>
|
|
91
|
-
<td class="sub-vars__name">retryAfter</td>
|
|
92
|
-
<td class="sub-vars__type">number seconds | null</td>
|
|
93
|
-
<td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
|
|
94
|
-
<td>後端 429 帶回的下次可生成時間;按鈕禁用倒數的依據,業務上提示使用者「節流而非系統故障」。</td>
|
|
95
|
-
</tr>
|
|
96
|
-
<tr>
|
|
97
|
-
<td class="sub-vars__name">Result</td>
|
|
98
|
-
<td class="sub-vars__type">{ok: NewCode} | {err: UiError}</td>
|
|
99
|
-
<td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
|
|
100
|
-
<td>一次「生成」呼叫結果;驅動是否要把新碼樂觀插入 <code>list</code>,或顯示錯誤 banner。</td>
|
|
101
|
-
</tr>
|
|
102
|
-
<tr>
|
|
103
|
-
<td class="sub-vars__name">UiMessage</td>
|
|
104
|
-
<td class="sub-vars__type">{lang_tag, text}</td>
|
|
105
|
-
<td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
|
|
106
|
-
<td>額度/網路錯誤的本地化呈現;使用者透過此訊息知道是自己的額度問題還是系統暫時不可用。</td>
|
|
107
|
-
</tr>
|
|
108
|
-
</tbody>
|
|
109
|
-
</table>
|
|
110
|
-
</section>
|
|
111
|
-
|
|
112
|
-
<section class="sub-dataflow">
|
|
113
|
-
<h2>內部資料流</h2>
|
|
114
|
-
<ol>
|
|
115
|
-
<li>頁面掛載 → <code>useInviteCodes</code> 取得 <code>list</code> 顯示。</li>
|
|
116
|
-
<li>使用者點生成 → <code>createInviteCode</code>:UI <code>idle → loading</code>。</li>
|
|
117
|
-
<li><code>Result.ok</code> → 把 <code>NewCode</code> prepend 到 <code>list</code>(保持其餘元素穩定)。</li>
|
|
118
|
-
<li><code>Result.err</code> 為 quota → <code>formatQuotaError</code> 顯示 <code>UiMessage</code>,<code>list</code> 不變。</li>
|
|
119
|
-
<li>使用者選擇 <code>copyCodeToClipboard(code)</code>,不影響資料模型。</li>
|
|
120
|
-
</ol>
|
|
121
|
-
</section>
|
|
122
|
-
|
|
123
|
-
<section class="sub-errors">
|
|
124
|
-
<h2>本子模組可暴露的錯誤</h2>
|
|
125
|
-
<table>
|
|
126
|
-
<thead><tr><th>來源</th><th>UI 行為</th></tr></thead>
|
|
127
|
-
<tbody>
|
|
128
|
-
<tr><td>未登入(401)</td><td>路由守衛攔截 → 導向登入;本子模組不顯示業務錯誤。</td></tr>
|
|
129
|
-
<tr><td>quota(429)</td><td><code>formatQuotaError</code> banner;按鈕短暫禁用至 <code>retryAfter</code>。</td></tr>
|
|
130
|
-
<tr><td>網路錯誤</td><td>泛用提示;可重試。</td></tr>
|
|
131
|
-
</tbody>
|
|
132
|
-
</table>
|
|
133
|
-
</section>
|
|
134
|
-
|
|
135
|
-
<footer class="atlas-meta" style="margin-top: 2rem">
|
|
136
|
-
<p><a href="../../index.html">← 宏觀架構</a> · <a href="./index.html">← 功能總覽</a></p>
|
|
137
|
-
</footer>
|
|
138
|
-
</main>
|
|
139
|
-
</body>
|
|
2
|
+
<html lang="en" data-atlas-page="submodule">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Get invite codes · web-get-invite-ui</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<link rel="stylesheet" href="../../assets/architecture.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header class="submodule-header">
|
|
11
|
+
<nav class="submodule-breadcrumb"><a href="../../index.html">← Atlas</a> · <a href="index.html">← Get invite codes</a></nav>
|
|
12
|
+
<h1>web-get-invite-ui <small class="submodule-kind submodule-kind--ui">UI</small></h1>
|
|
13
|
+
<p class="submodule-role">React page that lets a signed-in member request a new invite code.</p>
|
|
14
|
+
</header>
|
|
15
|
+
<main class="submodule-main">
|
|
16
|
+
<section class="sub-io" aria-label="Function I/O">
|
|
17
|
+
<h2>Function I/O</h2>
|
|
18
|
+
<table class="sub-table">
|
|
19
|
+
<thead><tr><th scope="col">Name</th><th scope="col">In</th><th scope="col">Out</th><th scope="col">Side</th><th scope="col">Purpose</th></tr></thead>
|
|
20
|
+
<tbody>
|
|
21
|
+
<tr><td>handleGenerate</td><td>MouseEvent</td><td>void</td><td>io</td><td>Calls POST /api/invites and renders the returned code.</td></tr>
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>
|
|
24
|
+
</section>
|
|
25
|
+
<section class="sub-vars" aria-label="Variables">
|
|
26
|
+
<h2>Variables</h2>
|
|
27
|
+
<table class="sub-table">
|
|
28
|
+
<thead><tr><th scope="col">Name</th><th scope="col">Type</th><th scope="col">Scope</th><th scope="col">Purpose</th></tr></thead>
|
|
29
|
+
<tbody>
|
|
30
|
+
<tr><td>userId</td><td>string</td><td>call</td><td>Identifies the member who will own the new invite row.</td></tr>
|
|
31
|
+
<tr><td>code</td><td>string</td><td>instance</td><td>Last issued code shown to the member.</td></tr>
|
|
32
|
+
</tbody>
|
|
33
|
+
</table>
|
|
34
|
+
</section>
|
|
35
|
+
<section class="sub-dataflow" aria-label="Internal data flow">
|
|
36
|
+
<h2>Internal data flow</h2>
|
|
37
|
+
<svg class="sub-dataflow__svg" viewBox="0 0 400 264" role="img" aria-label="Internal dataflow">
|
|
38
|
+
<defs><marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker></defs>
|
|
39
|
+
<g class="sub-dataflow__step">
|
|
40
|
+
<rect x="20" y="20" width="360" height="56" rx="8" ry="8" />
|
|
41
|
+
<text x="200" y="44" text-anchor="middle">Read userId from auth context.</text>
|
|
42
|
+
</g>
|
|
43
|
+
<line class="sub-dataflow__arrow" x1="200" y1="76" x2="200" y2="104" marker-end="url(#sub-arrow)" />
|
|
44
|
+
<g class="sub-dataflow__step">
|
|
45
|
+
<rect x="20" y="104" width="360" height="56" rx="8" ry="8" />
|
|
46
|
+
<text x="200" y="128" text-anchor="middle">POST /api/invites with userId.</text>
|
|
47
|
+
</g>
|
|
48
|
+
<line class="sub-dataflow__arrow" x1="200" y1="160" x2="200" y2="188" marker-end="url(#sub-arrow)" />
|
|
49
|
+
<g class="sub-dataflow__step">
|
|
50
|
+
<rect x="20" y="188" width="360" height="56" rx="8" ry="8" />
|
|
51
|
+
<text x="200" y="212" text-anchor="middle">Render returned code or surface error.</text>
|
|
52
|
+
</g>
|
|
53
|
+
</svg>
|
|
54
|
+
</section>
|
|
55
|
+
<section class="sub-errors" aria-label="Errors">
|
|
56
|
+
<h2>Errors</h2>
|
|
57
|
+
<table class="sub-table">
|
|
58
|
+
<thead><tr><th scope="col">Name</th><th scope="col">When</th><th scope="col">Means</th></tr></thead>
|
|
59
|
+
<tbody>
|
|
60
|
+
<tr><td>ErrUnauthenticated</td><td>Session is missing on the client.</td><td>Redirect to sign-in.</td></tr>
|
|
61
|
+
<tr><td>ErrIssuanceFailed</td><td>API returns 500.</td><td>Show retry banner.</td></tr>
|
|
62
|
+
</tbody>
|
|
63
|
+
</table>
|
|
64
|
+
</section>
|
|
65
|
+
</main>
|
|
66
|
+
</body>
|
|
140
67
|
</html>
|
|
@@ -1,53 +1,63 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<
|
|
14
|
-
</
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
</
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</
|
|
45
|
-
</
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
2
|
+
<html lang="en" data-atlas-page="feature">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Invite-code registration</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<link rel="stylesheet" href="../../assets/architecture.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header class="feature-header">
|
|
11
|
+
<nav class="feature-breadcrumb"><a href="../../index.html">← Atlas</a></nav>
|
|
12
|
+
<h1>Invite-code registration</h1>
|
|
13
|
+
<p class="feature-depends">Depends on: <a href="../get-invite-codes/index.html">get-invite-codes</a></p>
|
|
14
|
+
</header>
|
|
15
|
+
<main class="feature-main">
|
|
16
|
+
<section class="feature-story"><p>A visitor holding an invite code submits email/password plus the code. The registration service validates the code inside a single transaction; on success it creates a new user and consumes the invite_codes row issued earlier by `get-invite-codes`.</p></section>
|
|
17
|
+
<section class="feature-submodules" aria-label="Submodules">
|
|
18
|
+
<h2>Submodules</h2>
|
|
19
|
+
<ul class="submodule-nav">
|
|
20
|
+
<li class="submodule-card">
|
|
21
|
+
<a class="submodule-card__link" href="web-register-ui.html">
|
|
22
|
+
<span class="submodule-card__name">web-register-ui</span>
|
|
23
|
+
<span class="submodule-card__kind submodule-card__kind--ui">UI</span>
|
|
24
|
+
</a>
|
|
25
|
+
<p class="submodule-card__role">React page that captures email, password, and invite code.</p>
|
|
26
|
+
</li>
|
|
27
|
+
<li class="submodule-card">
|
|
28
|
+
<a class="submodule-card__link" href="public-api.html">
|
|
29
|
+
<span class="submodule-card__name">public-api</span>
|
|
30
|
+
<span class="submodule-card__kind submodule-card__kind--api">API</span>
|
|
31
|
+
</a>
|
|
32
|
+
<p class="submodule-card__role">HTTP boundary for `/api/register` POST requests.</p>
|
|
33
|
+
</li>
|
|
34
|
+
<li class="submodule-card">
|
|
35
|
+
<a class="submodule-card__link" href="registration-service.html">
|
|
36
|
+
<span class="submodule-card__name">registration-service</span>
|
|
37
|
+
<span class="submodule-card__kind submodule-card__kind--service">Service</span>
|
|
38
|
+
</a>
|
|
39
|
+
<p class="submodule-card__role">Owns the registration transaction (consumer side of the invite_codes data row).</p>
|
|
40
|
+
</li>
|
|
41
|
+
<li class="submodule-card">
|
|
42
|
+
<a class="submodule-card__link" href="postgresql.html">
|
|
43
|
+
<span class="submodule-card__name">postgresql</span>
|
|
44
|
+
<span class="submodule-card__kind submodule-card__kind--db">DB</span>
|
|
45
|
+
</a>
|
|
46
|
+
<p class="submodule-card__role">Stores `users` rows and applies invite-code state transitions inside the registration tx.</p>
|
|
47
|
+
</li>
|
|
48
|
+
</ul>
|
|
49
|
+
</section>
|
|
50
|
+
<section class="feature-edges" aria-label="Intra-feature edges">
|
|
51
|
+
<h2>Intra-feature edges</h2>
|
|
52
|
+
<ul class="feature-edges__list">
|
|
53
|
+
<li class="feature-edges__item feature-edges__item--call"><span class="feature-edges__endpoints">web-register-ui → public-api</span><span class="feature-edges__kind">call</span><span class="feature-edges__label">POST /api/register</span></li>
|
|
54
|
+
<li class="feature-edges__item feature-edges__item--call"><span class="feature-edges__endpoints">public-api → registration-service</span><span class="feature-edges__kind">call</span><span class="feature-edges__label">RegisterWithInvite(ctx, RegisterInput)</span></li>
|
|
55
|
+
<li class="feature-edges__item feature-edges__item--call"><span class="feature-edges__endpoints">registration-service → postgresql</span><span class="feature-edges__kind">call</span><span class="feature-edges__label">SELECT invite_codes FOR UPDATE</span></li>
|
|
56
|
+
<li class="feature-edges__item feature-edges__item--call"><span class="feature-edges__endpoints">registration-service → postgresql</span><span class="feature-edges__kind">call</span><span class="feature-edges__label">INSERT users</span></li>
|
|
57
|
+
<li class="feature-edges__item feature-edges__item--call"><span class="feature-edges__endpoints">registration-service → postgresql</span><span class="feature-edges__kind">call</span><span class="feature-edges__label">UPDATE invite_codes.consumed_at</span></li>
|
|
58
|
+
<li class="feature-edges__item feature-edges__item--return"><span class="feature-edges__endpoints">postgresql → registration-service</span><span class="feature-edges__kind">return</span><span class="feature-edges__label">row | rows_affected</span></li>
|
|
59
|
+
</ul>
|
|
60
|
+
</section>
|
|
61
|
+
</main>
|
|
62
|
+
</body>
|
|
53
63
|
</html>
|
|
@@ -1,161 +1,68 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<
|
|
14
|
-
</
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<section class="sub-vars">
|
|
69
|
-
<h2>變數與業務用途</h2>
|
|
70
|
-
<p class="sub-vars__intro">本表分兩段:上半為 SQL 函式參數,下半為 <code>invite_codes</code> / <code>users</code> 表上的關鍵列欄位(被本子模組讀寫)。</p>
|
|
71
|
-
<table>
|
|
72
|
-
<thead><tr><th>變數</th><th>型別</th><th>作用域</th><th>業務用途</th></tr></thead>
|
|
73
|
-
<tbody>
|
|
74
|
-
<tr>
|
|
75
|
-
<td class="sub-vars__name">tx</td>
|
|
76
|
-
<td class="sub-vars__type">*sql.Tx</td>
|
|
77
|
-
<td><span class="sub-vars__scope sub-vars__scope--tx">tx</span></td>
|
|
78
|
-
<td>上層管理的隔離單元;本子模組所有 SQL 都掛在這個 <code>tx</code> 上,未 commit 即視為「無事發生」,保證註冊原子性。</td>
|
|
79
|
-
</tr>
|
|
80
|
-
<tr>
|
|
81
|
-
<td class="sub-vars__name">code</td>
|
|
82
|
-
<td class="sub-vars__type">text</td>
|
|
83
|
-
<td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
|
|
84
|
-
<td><code>GetInviteForUpdate</code> 的查鎖鍵;對應到列上欄位 <code>invite_codes.code</code> 的 UNIQUE 索引,避免並行核銷相同邀請。</td>
|
|
85
|
-
</tr>
|
|
86
|
-
<tr>
|
|
87
|
-
<td class="sub-vars__name">inviteID</td>
|
|
88
|
-
<td class="sub-vars__type">UUID</td>
|
|
89
|
-
<td><span class="sub-vars__scope sub-vars__scope--tx">tx</span></td>
|
|
90
|
-
<td>從 SELECT 取得後跨多個寫入;把「核銷哪一張邀請」與「新 user 屬於哪張邀請」綁定,作為日後審計鏈。</td>
|
|
91
|
-
</tr>
|
|
92
|
-
<tr>
|
|
93
|
-
<td class="sub-vars__name">userID</td>
|
|
94
|
-
<td class="sub-vars__type">UUID</td>
|
|
95
|
-
<td><span class="sub-vars__scope sub-vars__scope--tx">tx</span></td>
|
|
96
|
-
<td><code>InsertUser RETURNING id</code> 的結果;同一 <code>tx</code> 內回填到 <code>consumed_by</code>,完成「誰用了邀請」的紀錄。</td>
|
|
97
|
-
</tr>
|
|
98
|
-
<tr>
|
|
99
|
-
<td class="sub-vars__name">now</td>
|
|
100
|
-
<td class="sub-vars__type">timestamp</td>
|
|
101
|
-
<td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
|
|
102
|
-
<td>核銷時的官方時間;由應用層提供以避免應用與 DB 時鐘漂移影響審計順序。</td>
|
|
103
|
-
</tr>
|
|
104
|
-
<tr>
|
|
105
|
-
<td class="sub-vars__name">RowsAffected</td>
|
|
106
|
-
<td class="sub-vars__type">int</td>
|
|
107
|
-
<td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
|
|
108
|
-
<td><code>MarkInviteConsumed</code> 條件更新結果;<code>0</code> 表示已被搶先核銷,本次必須回滾,否則會發生雙倍消費。</td>
|
|
109
|
-
</tr>
|
|
110
|
-
<tr>
|
|
111
|
-
<td class="sub-vars__name">consumed_at</td>
|
|
112
|
-
<td class="sub-vars__type">timestamp NULL</td>
|
|
113
|
-
<td><span class="sub-vars__scope sub-vars__scope--persist">persist</span></td>
|
|
114
|
-
<td><code>invite_codes</code> 列上欄位;非空代表「這張邀請已被消費」,與 <code>WHERE consumed_at IS NULL</code> 一起防雙重核銷。</td>
|
|
115
|
-
</tr>
|
|
116
|
-
<tr>
|
|
117
|
-
<td class="sub-vars__name">consumed_by</td>
|
|
118
|
-
<td class="sub-vars__type">FK → users.id</td>
|
|
119
|
-
<td><span class="sub-vars__scope sub-vars__scope--persist">persist</span></td>
|
|
120
|
-
<td><code>invite_codes</code> 列上欄位;紀錄是哪個 user 用掉這張邀請,是「邀請碼註冊」這條業務路徑留下的最終痕跡。</td>
|
|
121
|
-
</tr>
|
|
122
|
-
<tr>
|
|
123
|
-
<td class="sub-vars__name">users.email</td>
|
|
124
|
-
<td class="sub-vars__type">text UNIQUE</td>
|
|
125
|
-
<td><span class="sub-vars__scope sub-vars__scope--persist">persist</span></td>
|
|
126
|
-
<td>使用者唯一鍵;UNIQUE 違例代表「相同信箱已註冊」,業務上以 409 回應,呼叫端不應靜默忽略。</td>
|
|
127
|
-
</tr>
|
|
128
|
-
</tbody>
|
|
129
|
-
</table>
|
|
130
|
-
</section>
|
|
131
|
-
|
|
132
|
-
<section class="sub-dataflow">
|
|
133
|
-
<h2>內部資料流(單一交易視角)</h2>
|
|
134
|
-
<ol>
|
|
135
|
-
<li>呼叫端開 <code>tx</code> → 本子模組以該 <code>tx</code> 執行 SQL;交易控制不在本子模組內。</li>
|
|
136
|
-
<li><code>GetInviteForUpdate</code> 取得 <code>InviteRow</code> 並把 <strong>列鎖</strong> 綁在 <code>tx</code> 上。</li>
|
|
137
|
-
<li><code>InsertUser</code> 取得 <code>UserID</code>,作為下一步 <code>consumed_by</code> 的 FK 值。</li>
|
|
138
|
-
<li><code>MarkInviteConsumed</code> 以 <code>WHERE consumed_at IS NULL</code> 防雙重核銷。</li>
|
|
139
|
-
<li>所有寫入皆在 <code>tx</code> 內;commit/rollback 由呼叫端決定,本子模組對外只報告每個 SQL 的結果。</li>
|
|
140
|
-
</ol>
|
|
141
|
-
</section>
|
|
142
|
-
|
|
143
|
-
<section class="sub-errors">
|
|
144
|
-
<h2>本子模組會回傳的錯誤</h2>
|
|
145
|
-
<table>
|
|
146
|
-
<thead><tr><th>錯誤</th><th>觸發 SQL</th><th>含義</th></tr></thead>
|
|
147
|
-
<tbody>
|
|
148
|
-
<tr><td><code>NotFound</code></td><td><code>GetInviteForUpdate</code></td><td>無該邀請碼列。</td></tr>
|
|
149
|
-
<tr><td><code>UniqueViolation</code></td><td><code>InsertUser</code></td><td>email 重複。</td></tr>
|
|
150
|
-
<tr><td><code>LockWaitTimeout</code></td><td><code>GetInviteForUpdate</code></td><td>其他並發交易長時間持有鎖。</td></tr>
|
|
151
|
-
<tr><td><code>ConnTransient</code></td><td>任意</td><td>網路/restart;交易不會 partial。</td></tr>
|
|
152
|
-
</tbody>
|
|
153
|
-
</table>
|
|
154
|
-
</section>
|
|
155
|
-
|
|
156
|
-
<footer class="atlas-meta" style="margin-top: 2rem">
|
|
157
|
-
<p><a href="../../index.html">← 宏觀架構</a> · <a href="./index.html">← 功能總覽</a></p>
|
|
158
|
-
</footer>
|
|
159
|
-
</main>
|
|
160
|
-
</body>
|
|
2
|
+
<html lang="en" data-atlas-page="submodule">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Invite-code registration · postgresql</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<link rel="stylesheet" href="../../assets/architecture.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header class="submodule-header">
|
|
11
|
+
<nav class="submodule-breadcrumb"><a href="../../index.html">← Atlas</a> · <a href="index.html">← Invite-code registration</a></nav>
|
|
12
|
+
<h1>postgresql <small class="submodule-kind submodule-kind--db">DB</small></h1>
|
|
13
|
+
<p class="submodule-role">Stores `users` rows and applies invite-code state transitions inside the registration tx.</p>
|
|
14
|
+
</header>
|
|
15
|
+
<main class="submodule-main">
|
|
16
|
+
<section class="sub-io" aria-label="Function I/O">
|
|
17
|
+
<h2>Function I/O</h2>
|
|
18
|
+
<table class="sub-table">
|
|
19
|
+
<thead><tr><th scope="col">Name</th><th scope="col">In</th><th scope="col">Out</th><th scope="col">Side</th><th scope="col">Purpose</th></tr></thead>
|
|
20
|
+
<tbody>
|
|
21
|
+
<tr><td>SELECT_invite_for_update</td><td>code</td><td>InviteCodeRow | nil</td><td>lock</td><td>Locks the invite row for the duration of the transaction.</td></tr>
|
|
22
|
+
<tr><td>INSERT_users</td><td>userRow</td><td>userId</td><td>write</td><td>Persists the new account.</td></tr>
|
|
23
|
+
<tr><td>UPDATE_invite_consumed</td><td>code, now</td><td>rows_affected</td><td>write</td><td>Marks the invite as consumed.</td></tr>
|
|
24
|
+
</tbody>
|
|
25
|
+
</table>
|
|
26
|
+
</section>
|
|
27
|
+
<section class="sub-vars" aria-label="Variables">
|
|
28
|
+
<h2>Variables</h2>
|
|
29
|
+
<table class="sub-table">
|
|
30
|
+
<thead><tr><th scope="col">Name</th><th scope="col">Type</th><th scope="col">Scope</th><th scope="col">Purpose</th></tr></thead>
|
|
31
|
+
<tbody>
|
|
32
|
+
<tr><td>users.email</td><td>text</td><td>persist</td><td>Unique account identity.</td></tr>
|
|
33
|
+
<tr><td>invite_codes.consumed_at</td><td>timestamptz</td><td>persist</td><td>Timestamp set the moment the invite row is consumed.</td></tr>
|
|
34
|
+
</tbody>
|
|
35
|
+
</table>
|
|
36
|
+
</section>
|
|
37
|
+
<section class="sub-dataflow" aria-label="Internal data flow">
|
|
38
|
+
<h2>Internal data flow</h2>
|
|
39
|
+
<svg class="sub-dataflow__svg" viewBox="0 0 400 264" role="img" aria-label="Internal dataflow">
|
|
40
|
+
<defs><marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker></defs>
|
|
41
|
+
<g class="sub-dataflow__step">
|
|
42
|
+
<rect x="20" y="20" width="360" height="56" rx="8" ry="8" />
|
|
43
|
+
<text x="200" y="44" text-anchor="middle">Apply row lock on invite_codes.</text>
|
|
44
|
+
</g>
|
|
45
|
+
<line class="sub-dataflow__arrow" x1="200" y1="76" x2="200" y2="104" marker-end="url(#sub-arrow)" />
|
|
46
|
+
<g class="sub-dataflow__step">
|
|
47
|
+
<rect x="20" y="104" width="360" height="56" rx="8" ry="8" />
|
|
48
|
+
<text x="200" y="128" text-anchor="middle">Validate uniqueness of users.email.</text>
|
|
49
|
+
</g>
|
|
50
|
+
<line class="sub-dataflow__arrow" x1="200" y1="160" x2="200" y2="188" marker-end="url(#sub-arrow)" />
|
|
51
|
+
<g class="sub-dataflow__step">
|
|
52
|
+
<rect x="20" y="188" width="360" height="56" rx="8" ry="8" />
|
|
53
|
+
<text x="200" y="212" text-anchor="middle">Write users row and update invite_codes.consumed_at.</text>
|
|
54
|
+
</g>
|
|
55
|
+
</svg>
|
|
56
|
+
</section>
|
|
57
|
+
<section class="sub-errors" aria-label="Errors">
|
|
58
|
+
<h2>Errors</h2>
|
|
59
|
+
<table class="sub-table">
|
|
60
|
+
<thead><tr><th scope="col">Name</th><th scope="col">When</th><th scope="col">Means</th></tr></thead>
|
|
61
|
+
<tbody>
|
|
62
|
+
<tr><td>ErrUniqueEmail</td><td>users.email unique constraint violated.</td><td>Bubble up so service can return ErrEmailTaken.</td></tr>
|
|
63
|
+
</tbody>
|
|
64
|
+
</table>
|
|
65
|
+
</section>
|
|
66
|
+
</main>
|
|
67
|
+
</body>
|
|
161
68
|
</html>
|