@laitszkin/apollo-toolkit 3.10.0 → 3.11.1
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 +37 -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 +117 -125
- package/init-project-html/agents/openai.yaml +18 -9
- package/init-project-html/lib/atlas/assets/architecture.css +161 -0
- package/init-project-html/lib/atlas/assets/viewer.client.js +136 -0
- package/init-project-html/lib/atlas/cli.js +1023 -0
- package/init-project-html/lib/atlas/layout.js +330 -0
- package/init-project-html/lib/atlas/render.js +583 -0
- package/init-project-html/lib/atlas/schema.js +347 -0
- package/init-project-html/lib/atlas/state.js +402 -0
- package/init-project-html/references/TEMPLATE_SPEC.md +140 -83
- package/init-project-html/sample-demo/resources/project-architecture/assets/architecture.css +160 -1058
- package/init-project-html/sample-demo/resources/project-architecture/assets/viewer.client.js +136 -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 +172 -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 +64 -163
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-issuance-service.html +102 -196
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/postgresql.html +82 -163
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/public-api.html +88 -150
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/web-get-invite-ui.html +83 -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 +84 -159
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/public-api.html +81 -143
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/registration-service.html +98 -188
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/web-register-ui.html +83 -138
- package/init-project-html/sample-demo/resources/project-architecture/index.html +256 -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 +74 -67
- package/spec-to-project-html/agents/openai.yaml +14 -8
- package/spec-to-project-html/references/TEMPLATE_SPEC.md +98 -83
- package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
|
@@ -1,165 +1,66 @@
|
|
|
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
|
-
<td>每 N 位插入 <code>-</code>,便於人讀。</td>
|
|
67
|
-
</tr>
|
|
68
|
-
</tbody>
|
|
69
|
-
</table>
|
|
70
|
-
</section>
|
|
71
|
-
|
|
72
|
-
<section class="sub-vars">
|
|
73
|
-
<h2>變數與業務用途</h2>
|
|
74
|
-
<p class="sub-vars__intro">本子模組屬於純函式工具;下表分兩段:上半為建構時固定的設定,下半為單次 <code>Next</code> 期間的臨時值。</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">Config</td>
|
|
80
|
-
<td class="sub-vars__type">{bits, alphabet, groupSize}</td>
|
|
81
|
-
<td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
|
|
82
|
-
<td>整個 Generator 生命週期固定;決定碼強度(bits)、可讀性(alphabet)、人讀分組(groupSize)。</td>
|
|
83
|
-
</tr>
|
|
84
|
-
<tr>
|
|
85
|
-
<td class="sub-vars__name">bits</td>
|
|
86
|
-
<td class="sub-vars__type">int</td>
|
|
87
|
-
<td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
|
|
88
|
-
<td>熵預算;決定隨機字串對 UNIQUE 碰撞的容忍度;過低會推高呼叫端的重試次數與失敗率。</td>
|
|
89
|
-
</tr>
|
|
90
|
-
<tr>
|
|
91
|
-
<td class="sub-vars__name">alphabet</td>
|
|
92
|
-
<td class="sub-vars__type">string</td>
|
|
93
|
-
<td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
|
|
94
|
-
<td>字元表;剃除 0/O、1/I/l 等易混字,業務上降低使用者抄錄錯誤率,間接提高分享成功率。</td>
|
|
95
|
-
</tr>
|
|
96
|
-
<tr>
|
|
97
|
-
<td class="sub-vars__name">groupSize</td>
|
|
98
|
-
<td class="sub-vars__type">int</td>
|
|
99
|
-
<td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
|
|
100
|
-
<td>每幾個字元插入連字號;只影響顯示/輸入體驗,不影響熵或唯一性。</td>
|
|
101
|
-
</tr>
|
|
102
|
-
<tr>
|
|
103
|
-
<td class="sub-vars__name">bytes / buf</td>
|
|
104
|
-
<td class="sub-vars__type">[]byte</td>
|
|
105
|
-
<td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
|
|
106
|
-
<td>從 <code>crypto/rand</code> 讀出的原始熵;只在單次 <code>Next</code> 期間活著,不持久化、不外傳,避免被推測下一次值。</td>
|
|
107
|
-
</tr>
|
|
108
|
-
<tr>
|
|
109
|
-
<td class="sub-vars__name">raw</td>
|
|
110
|
-
<td class="sub-vars__type">string</td>
|
|
111
|
-
<td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
|
|
112
|
-
<td>經 base32 編碼後尚未分組的中介字串;只是內部步驟,不對外。</td>
|
|
113
|
-
</tr>
|
|
114
|
-
<tr>
|
|
115
|
-
<td class="sub-vars__name">out / candidate</td>
|
|
116
|
-
<td class="sub-vars__type">string</td>
|
|
117
|
-
<td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
|
|
118
|
-
<td>最終回傳的候選碼;本子模組不快取、不記錄;唯一性由呼叫端結合 DB UNIQUE 保證。</td>
|
|
119
|
-
</tr>
|
|
120
|
-
</tbody>
|
|
121
|
-
</table>
|
|
122
|
-
</section>
|
|
123
|
-
|
|
124
|
-
<section class="sub-dataflow">
|
|
125
|
-
<h2>內部資料流(單次 Next)</h2>
|
|
126
|
-
<ol>
|
|
127
|
-
<li>從 <code>crypto/rand</code> 讀 <code>bytes[ceil(bits/8)]</code>。</li>
|
|
128
|
-
<li><code>bytes</code> → <code>encode(bytes, alphabet)</code> → <code>raw: string</code>。</li>
|
|
129
|
-
<li><code>raw</code> → <code>group(raw, groupSize)</code> → 最終字串。</li>
|
|
130
|
-
<li>內部不持有狀態(除設定);多次呼叫互相獨立,無快取無記錄。</li>
|
|
131
|
-
</ol>
|
|
132
|
-
<svg class="sub-dataflow__svg" viewBox="0 0 720 110" role="img" aria-label="生成器資料流">
|
|
133
|
-
<defs>
|
|
134
|
-
<marker id="d-arr3" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto">
|
|
135
|
-
<path d="M0,0 L9,4.5 L0,9 Z" fill="currentColor"></path>
|
|
136
|
-
</marker>
|
|
137
|
-
</defs>
|
|
138
|
-
<g class="d-node"><rect x="20" y="30" width="150" height="50" rx="8"></rect><text x="95" y="50" text-anchor="middle">crypto/rand</text><text x="95" y="68" text-anchor="middle" class="d-label">→ bytes</text></g>
|
|
139
|
-
<g class="d-node"><rect x="220" y="30" width="150" height="50" rx="8"></rect><text x="295" y="50" text-anchor="middle">encode</text><text x="295" y="68" text-anchor="middle" class="d-label">bytes → raw</text></g>
|
|
140
|
-
<g class="d-node"><rect x="420" y="30" width="150" height="50" rx="8"></rect><text x="495" y="50" text-anchor="middle">group</text><text x="495" y="68" text-anchor="middle" class="d-label">raw → 字串</text></g>
|
|
141
|
-
<g class="d-node"><rect x="600" y="30" width="100" height="50" rx="8"></rect><text x="650" y="58" text-anchor="middle">candidate</text></g>
|
|
142
|
-
<line class="d-edge" x1="170" y1="55" x2="216" y2="55" marker-end="url(#d-arr3)"></line>
|
|
143
|
-
<line class="d-edge" x1="370" y1="55" x2="416" y2="55" marker-end="url(#d-arr3)"></line>
|
|
144
|
-
<line class="d-edge" x1="570" y1="55" x2="596" y2="55" marker-end="url(#d-arr3)"></line>
|
|
145
|
-
</svg>
|
|
146
|
-
</section>
|
|
147
|
-
|
|
148
|
-
<section class="sub-errors">
|
|
149
|
-
<h2>本子模組會拋出的錯誤</h2>
|
|
150
|
-
<table>
|
|
151
|
-
<thead><tr><th>錯誤</th><th>條件</th></tr></thead>
|
|
152
|
-
<tbody>
|
|
153
|
-
<tr><td><code>ErrEntropy</code></td><td><code>crypto/rand.Read</code> 短讀/失敗(罕見)。</td></tr>
|
|
154
|
-
<tr><td><code>ErrConfig</code></td><td><code>NewGenerator</code> 收到非法 <code>Config</code>。</td></tr>
|
|
155
|
-
</tbody>
|
|
156
|
-
</table>
|
|
157
|
-
<p class="atlas-meta">注意:「唯一性」<strong>不在本子模組</strong>;DB UNIQUE 違例由呼叫端處理(重新呼叫 <code>Next</code>)。</p>
|
|
158
|
-
</section>
|
|
159
|
-
|
|
160
|
-
<footer class="atlas-meta" style="margin-top: 2rem">
|
|
161
|
-
<p><a href="../../index.html">← 宏觀架構</a> · <a href="./index.html">← 功能總覽</a></p>
|
|
162
|
-
</footer>
|
|
163
|
-
</main>
|
|
164
|
-
</body>
|
|
2
|
+
<html lang="en" data-atlas-page="submodule">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Get invite codes · invite-code-generator</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>invite-code-generator <small class="submodule-kind submodule-kind--pure-fn">Pure fn</small></h1>
|
|
13
|
+
<p class="submodule-role">Pure helper that turns random bytes into a printable 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>encode</td><td>bytes[8]</td><td>code</td><td>pure</td><td>Base32 encoding without padding for human-readable codes.</td></tr>
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>
|
|
24
|
+
</section>
|
|
25
|
+
<section class="sub-vars" aria-label="Variables">
|
|
26
|
+
<h2>Variables</h2>
|
|
27
|
+
<p class="sub-section__empty">No variables recorded.</p>
|
|
28
|
+
</section>
|
|
29
|
+
<section class="sub-dataflow" aria-label="Internal data flow">
|
|
30
|
+
<h2>Internal data flow</h2>
|
|
31
|
+
<div class="sub-dataflow__canvas" data-pan-zoom-container>
|
|
32
|
+
<div class="sub-dataflow__toolbar" role="toolbar" aria-label="Diagram controls">
|
|
33
|
+
<button type="button" data-pan-zoom="zoom-in" aria-label="Zoom in">+</button>
|
|
34
|
+
<button type="button" data-pan-zoom="zoom-out" aria-label="Zoom out">−</button>
|
|
35
|
+
<button type="button" data-pan-zoom="fit" aria-label="Reset view">Fit</button>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="sub-dataflow__viewport" data-pan-zoom-viewport>
|
|
38
|
+
<svg class="sub-dataflow__svg" data-atlas-svg="sub-dataflow" viewBox="0 0 628 252" role="img" aria-label="Internal dataflow">
|
|
39
|
+
<defs>
|
|
40
|
+
<marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker>
|
|
41
|
+
</defs>
|
|
42
|
+
<g class="sub-dataflow__step">
|
|
43
|
+
<circle class="sub-dataflow__badge" cx="42" cy="68" r="18" />
|
|
44
|
+
<text class="sub-dataflow__badge-text" x="42" y="73" text-anchor="middle">1</text>
|
|
45
|
+
<rect class="sub-dataflow__box" x="80" y="32" width="520" height="72" rx="14" ry="14" />
|
|
46
|
+
<text class="sub-dataflow__text" x="340" y="74" text-anchor="middle">Read 8 random bytes.</text>
|
|
47
|
+
</g>
|
|
48
|
+
<line class="sub-dataflow__arrow" x1="340" y1="110" x2="340" y2="140" marker-end="url(#sub-arrow)" />
|
|
49
|
+
<g class="sub-dataflow__step">
|
|
50
|
+
<circle class="sub-dataflow__badge" cx="42" cy="184" r="18" />
|
|
51
|
+
<text class="sub-dataflow__badge-text" x="42" y="189" text-anchor="middle">2</text>
|
|
52
|
+
<rect class="sub-dataflow__box" x="80" y="148" width="520" height="72" rx="14" ry="14" />
|
|
53
|
+
<text class="sub-dataflow__text" x="340" y="190" text-anchor="middle">Base32 encode without padding.</text>
|
|
54
|
+
</g>
|
|
55
|
+
</svg>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</section>
|
|
59
|
+
<section class="sub-errors" aria-label="Errors">
|
|
60
|
+
<h2>Errors</h2>
|
|
61
|
+
<p class="sub-section__empty">No errors recorded.</p>
|
|
62
|
+
</section>
|
|
63
|
+
</main>
|
|
64
|
+
<script src="../../assets/viewer.client.js" defer></script>
|
|
65
|
+
</body>
|
|
165
66
|
</html>
|
|
@@ -1,198 +1,104 @@
|
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<td class="sub-vars__type">int</td>
|
|
105
|
-
<td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
|
|
106
|
-
<td>滑動視窗額度上限;產品設定,跨呼叫不變;改值代表產品策略改變,需配套更新文案。</td>
|
|
107
|
-
</tr>
|
|
108
|
-
<tr>
|
|
109
|
-
<td class="sub-vars__name">userStatus</td>
|
|
110
|
-
<td class="sub-vars__type">active | banned | suspended</td>
|
|
111
|
-
<td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
|
|
112
|
-
<td>帳號是否處於可發放狀態;停權或暫停狀態下立即拒絕,不浪費 generator/DB 資源。</td>
|
|
113
|
-
</tr>
|
|
114
|
-
<tr>
|
|
115
|
-
<td class="sub-vars__name">now</td>
|
|
116
|
-
<td class="sub-vars__type">time.Time</td>
|
|
117
|
-
<td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
|
|
118
|
-
<td>滑動視窗起點計算與 <code>created_at</code> 的基準;測試可注入。</td>
|
|
119
|
-
</tr>
|
|
120
|
-
<tr>
|
|
121
|
-
<td class="sub-vars__name">attempt</td>
|
|
122
|
-
<td class="sub-vars__type">int</td>
|
|
123
|
-
<td><span class="sub-vars__scope sub-vars__scope--loop">loop</span></td>
|
|
124
|
-
<td>UNIQUE 碰撞重試計數;搭配 <code>max</code> 限制最壞情況避免無限循環。</td>
|
|
125
|
-
</tr>
|
|
126
|
-
<tr>
|
|
127
|
-
<td class="sub-vars__name">max</td>
|
|
128
|
-
<td class="sub-vars__type">int</td>
|
|
129
|
-
<td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
|
|
130
|
-
<td>重試上限;超出即拋 <code>ErrCollisionExhausted</code>;上限與 generator 的熵預算共同決定罕見失敗率。</td>
|
|
131
|
-
</tr>
|
|
132
|
-
<tr>
|
|
133
|
-
<td class="sub-vars__name">candidate</td>
|
|
134
|
-
<td class="sub-vars__type">string</td>
|
|
135
|
-
<td><span class="sub-vars__scope sub-vars__scope--loop">loop</span></td>
|
|
136
|
-
<td>一輪重試取得的候選碼;尚未確定唯一;UNIQUE 違例即丟棄並重抽,不會被使用者看到。</td>
|
|
137
|
-
</tr>
|
|
138
|
-
<tr>
|
|
139
|
-
<td class="sub-vars__name">InviteCodeRecord</td>
|
|
140
|
-
<td class="sub-vars__type">{code, owner_user_id, allowed_registration, created_at}</td>
|
|
141
|
-
<td><span class="sub-vars__scope sub-vars__scope--loop">loop</span></td>
|
|
142
|
-
<td>寫入 DB 的承載;<code>allowed_registration=true</code> 是「邀請碼註冊」功能消費的關鍵旗標——關閉此值代表暫停發放但保留歷史紀錄。</td>
|
|
143
|
-
</tr>
|
|
144
|
-
</tbody>
|
|
145
|
-
</table>
|
|
146
|
-
</section>
|
|
147
|
-
|
|
148
|
-
<section class="sub-dataflow">
|
|
149
|
-
<h2>內部資料流</h2>
|
|
150
|
-
<ol>
|
|
151
|
-
<li>取得 <code>userID</code> + 開 <code>tx</code> → 讀<code>{used, userStatus}</code>(透過 DB 子模組;其 SQL 介面見宏觀圖邊 <code>e-B-...</code>)。</li>
|
|
152
|
-
<li><code>checkQuota</code> 不通過 → 拋 <code>ErrQuotaExceeded</code>/<code>ErrForbidden</code>;本函式直接結束(呼叫端負責回滾)。</li>
|
|
153
|
-
<li><code>candidate</code> ← 生成器產出的字串。</li>
|
|
154
|
-
<li><code>buildRecord(userID, candidate, now)</code> → <code>InviteCodeRecord</code>。</li>
|
|
155
|
-
<li>嘗試寫入;若 <code>err</code> 與 <code>shouldRetryOnCollision(err, attempt, max)</code> 為 <code>true</code>,<code>attempt+=1</code>,回到步驟 3。</li>
|
|
156
|
-
<li>寫入成功 → 提交 → 回傳 <code>InviteCodeRecord</code>。</li>
|
|
157
|
-
</ol>
|
|
158
|
-
<svg class="sub-dataflow__svg" viewBox="0 0 720 220" role="img" aria-label="內部資料流">
|
|
159
|
-
<defs>
|
|
160
|
-
<marker id="d-arr2" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto">
|
|
161
|
-
<path d="M0,0 L9,4.5 L0,9 Z" fill="currentColor"></path>
|
|
162
|
-
</marker>
|
|
163
|
-
</defs>
|
|
164
|
-
<g class="d-node"><rect x="20" y="30" width="150" height="50" rx="8"></rect><text x="95" y="58" text-anchor="middle">userID</text></g>
|
|
165
|
-
<g class="d-node"><rect x="200" y="30" width="150" height="50" rx="8"></rect><text x="275" y="50" text-anchor="middle">checkQuota</text><text x="275" y="68" text-anchor="middle" class="d-label">→ true | err</text></g>
|
|
166
|
-
<g class="d-node"><rect x="380" y="30" width="170" height="50" rx="8"></rect><text x="465" y="50" text-anchor="middle">candidate</text><text x="465" y="68" text-anchor="middle" class="d-label">← Generator.Next()</text></g>
|
|
167
|
-
<g class="d-node"><rect x="580" y="30" width="120" height="50" rx="8"></rect><text x="640" y="58" text-anchor="middle">buildRecord</text></g>
|
|
168
|
-
|
|
169
|
-
<g class="d-node"><rect x="380" y="140" width="170" height="50" rx="8"></rect><text x="465" y="160" text-anchor="middle">shouldRetryOnCollision</text><text x="465" y="178" text-anchor="middle" class="d-label">err+attempt → bool</text></g>
|
|
170
|
-
<g class="d-node"><rect x="580" y="140" width="120" height="50" rx="8"></rect><text x="640" y="168" text-anchor="middle">InviteCodeRecord</text></g>
|
|
171
|
-
|
|
172
|
-
<line class="d-edge" x1="170" y1="55" x2="196" y2="55" marker-end="url(#d-arr2)"></line>
|
|
173
|
-
<line class="d-edge" x1="350" y1="55" x2="376" y2="55" marker-end="url(#d-arr2)"></line>
|
|
174
|
-
<line class="d-edge" x1="550" y1="55" x2="576" y2="55" marker-end="url(#d-arr2)"></line>
|
|
175
|
-
<line class="d-edge" x1="640" y1="80" x2="640" y2="136" marker-end="url(#d-arr2)"></line>
|
|
176
|
-
<line class="d-edge d-edge--side" x1="465" y1="190" x2="465" y2="80" marker-end="url(#d-arr2)"></line>
|
|
177
|
-
</svg>
|
|
178
|
-
</section>
|
|
179
|
-
|
|
180
|
-
<section class="sub-errors">
|
|
181
|
-
<h2>本子模組會拋出的錯誤</h2>
|
|
182
|
-
<table>
|
|
183
|
-
<thead><tr><th>錯誤</th><th>條件</th></tr></thead>
|
|
184
|
-
<tbody>
|
|
185
|
-
<tr><td><code>ErrQuotaExceeded</code></td><td><code>checkQuota</code> 失敗(含 <code>retryAfter</code>)</td></tr>
|
|
186
|
-
<tr><td><code>ErrForbidden</code></td><td>帳號停用</td></tr>
|
|
187
|
-
<tr><td><code>ErrCollisionExhausted</code></td><td>重試次數用盡;極罕見,視熵設計。</td></tr>
|
|
188
|
-
<tr><td><code>ErrTransient</code></td><td>連線中斷/<code>ctx</code> 取消</td></tr>
|
|
189
|
-
</tbody>
|
|
190
|
-
</table>
|
|
191
|
-
</section>
|
|
192
|
-
|
|
193
|
-
<footer class="atlas-meta" style="margin-top: 2rem">
|
|
194
|
-
<p><a href="../../index.html">← 宏觀架構</a> · <a href="./index.html">← 功能總覽</a></p>
|
|
195
|
-
</footer>
|
|
196
|
-
</main>
|
|
197
|
-
</body>
|
|
2
|
+
<html lang="en" data-atlas-page="submodule">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Get invite codes · invite-issuance-service</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>invite-issuance-service <small class="submodule-kind submodule-kind--service">Service</small></h1>
|
|
13
|
+
<p class="submodule-role">Domain service that mints and persists a single invite row per request.</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>Issue</td><td>ctx, userId</td><td>InviteCode | error</td><td>tx</td><td>Generates a unique code and writes the matching invite_codes row.</td></tr>
|
|
22
|
+
<tr><td>generateCode</td><td>rand</td><td>code</td><td>pure</td><td>Produces a 10-char alphanumeric token.</td></tr>
|
|
23
|
+
</tbody>
|
|
24
|
+
</table>
|
|
25
|
+
</section>
|
|
26
|
+
<section class="sub-vars" aria-label="Variables">
|
|
27
|
+
<h2>Variables</h2>
|
|
28
|
+
<table class="sub-table">
|
|
29
|
+
<thead><tr><th scope="col">Name</th><th scope="col">Type</th><th scope="col">Scope</th><th scope="col">Purpose</th></tr></thead>
|
|
30
|
+
<tbody>
|
|
31
|
+
<tr><td>code</td><td>string</td><td>tx</td><td>Newly minted invite token recorded against the member.</td></tr>
|
|
32
|
+
<tr><td>row</td><td>InviteCodeRow</td><td>tx</td><td>Persisted invite_codes row carrying owner, code, expiry, and consumption state.</td></tr>
|
|
33
|
+
</tbody>
|
|
34
|
+
</table>
|
|
35
|
+
</section>
|
|
36
|
+
<section class="sub-dataflow" aria-label="Internal data flow">
|
|
37
|
+
<h2>Internal data flow</h2>
|
|
38
|
+
<div class="sub-dataflow__canvas" data-pan-zoom-container>
|
|
39
|
+
<div class="sub-dataflow__toolbar" role="toolbar" aria-label="Diagram controls">
|
|
40
|
+
<button type="button" data-pan-zoom="zoom-in" aria-label="Zoom in">+</button>
|
|
41
|
+
<button type="button" data-pan-zoom="zoom-out" aria-label="Zoom out">−</button>
|
|
42
|
+
<button type="button" data-pan-zoom="fit" aria-label="Reset view">Fit</button>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="sub-dataflow__viewport" data-pan-zoom-viewport>
|
|
45
|
+
<svg class="sub-dataflow__svg" data-atlas-svg="sub-dataflow" viewBox="0 0 628 626" role="img" aria-label="Internal dataflow">
|
|
46
|
+
<defs>
|
|
47
|
+
<marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker>
|
|
48
|
+
</defs>
|
|
49
|
+
<g class="sub-dataflow__step">
|
|
50
|
+
<circle class="sub-dataflow__badge" cx="42" cy="76" r="18" />
|
|
51
|
+
<text class="sub-dataflow__badge-text" x="42" y="81" text-anchor="middle">1</text>
|
|
52
|
+
<rect class="sub-dataflow__box" x="80" y="32" width="520" height="88" rx="14" ry="14" />
|
|
53
|
+
<rect class="sub-dataflow__fn-bg" x="94" y="46" width="79.2" height="20" rx="10" ry="10" />
|
|
54
|
+
<text class="sub-dataflow__fn-text" x="104" y="60">fn Issue</text>
|
|
55
|
+
<text class="sub-dataflow__text" x="340" y="98" text-anchor="middle">Open transaction.</text>
|
|
56
|
+
</g>
|
|
57
|
+
<line class="sub-dataflow__arrow" x1="340" y1="126" x2="340" y2="156" marker-end="url(#sub-arrow)" />
|
|
58
|
+
<g class="sub-dataflow__step">
|
|
59
|
+
<circle class="sub-dataflow__badge" cx="42" cy="221" r="18" />
|
|
60
|
+
<text class="sub-dataflow__badge-text" x="42" y="226" text-anchor="middle">2</text>
|
|
61
|
+
<rect class="sub-dataflow__box" x="80" y="164" width="520" height="114" rx="14" ry="14" />
|
|
62
|
+
<rect class="sub-dataflow__fn-bg" x="94" y="178" width="131" height="20" rx="10" ry="10" />
|
|
63
|
+
<text class="sub-dataflow__fn-text" x="104" y="192">fn generateCode</text>
|
|
64
|
+
<text class="sub-dataflow__text" x="340" y="230" text-anchor="middle">Build candidate code from random bytes.</text>
|
|
65
|
+
<text class="sub-dataflow__chip sub-dataflow__chip--writes" x="586" y="266" text-anchor="end">→ writes: code</text>
|
|
66
|
+
</g>
|
|
67
|
+
<line class="sub-dataflow__arrow" x1="340" y1="284" x2="340" y2="314" marker-end="url(#sub-arrow)" />
|
|
68
|
+
<g class="sub-dataflow__step">
|
|
69
|
+
<circle class="sub-dataflow__badge" cx="42" cy="379" r="18" />
|
|
70
|
+
<text class="sub-dataflow__badge-text" x="42" y="384" text-anchor="middle">3</text>
|
|
71
|
+
<rect class="sub-dataflow__box" x="80" y="322" width="520" height="114" rx="14" ry="14" />
|
|
72
|
+
<rect class="sub-dataflow__fn-bg" x="94" y="336" width="79.2" height="20" rx="10" ry="10" />
|
|
73
|
+
<text class="sub-dataflow__fn-text" x="104" y="350">fn Issue</text>
|
|
74
|
+
<text class="sub-dataflow__text" x="340" y="388" text-anchor="middle">Persist row via INSERT (retry on unique-violation).</text>
|
|
75
|
+
<text class="sub-dataflow__chip sub-dataflow__chip--reads" x="94" y="424">← reads: code</text>
|
|
76
|
+
<text class="sub-dataflow__chip sub-dataflow__chip--writes" x="586" y="424" text-anchor="end">→ writes: row</text>
|
|
77
|
+
</g>
|
|
78
|
+
<line class="sub-dataflow__arrow" x1="340" y1="442" x2="340" y2="472" marker-end="url(#sub-arrow)" />
|
|
79
|
+
<g class="sub-dataflow__step">
|
|
80
|
+
<circle class="sub-dataflow__badge" cx="42" cy="537" r="18" />
|
|
81
|
+
<text class="sub-dataflow__badge-text" x="42" y="542" text-anchor="middle">4</text>
|
|
82
|
+
<rect class="sub-dataflow__box" x="80" y="480" width="520" height="114" rx="14" ry="14" />
|
|
83
|
+
<rect class="sub-dataflow__fn-bg" x="94" y="494" width="79.2" height="20" rx="10" ry="10" />
|
|
84
|
+
<text class="sub-dataflow__fn-text" x="104" y="508">fn Issue</text>
|
|
85
|
+
<text class="sub-dataflow__text" x="340" y="546" text-anchor="middle">Commit transaction and return code to caller.</text>
|
|
86
|
+
<text class="sub-dataflow__chip sub-dataflow__chip--reads" x="94" y="582">← reads: code, row</text>
|
|
87
|
+
</g>
|
|
88
|
+
</svg>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</section>
|
|
92
|
+
<section class="sub-errors" aria-label="Errors">
|
|
93
|
+
<h2>Errors</h2>
|
|
94
|
+
<table class="sub-table">
|
|
95
|
+
<thead><tr><th scope="col">Name</th><th scope="col">When</th><th scope="col">Means</th></tr></thead>
|
|
96
|
+
<tbody>
|
|
97
|
+
<tr><td>ErrCollision</td><td>Unique constraint on code repeatedly violated.</td><td>Surface 503 after retry budget exhausted.</td></tr>
|
|
98
|
+
</tbody>
|
|
99
|
+
</table>
|
|
100
|
+
</section>
|
|
101
|
+
</main>
|
|
102
|
+
<script src="../../assets/viewer.client.js" defer></script>
|
|
103
|
+
</body>
|
|
198
104
|
</html>
|