@tapflowio/relay 0.1.0-alpha.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.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/RelayServer.d.ts +37 -0
  4. package/dist/RelayServer.d.ts.map +1 -0
  5. package/dist/RelayServer.js +531 -0
  6. package/dist/RelayServer.js.map +1 -0
  7. package/dist/SessionManager.d.ts +62 -0
  8. package/dist/SessionManager.d.ts.map +1 -0
  9. package/dist/SessionManager.js +174 -0
  10. package/dist/SessionManager.js.map +1 -0
  11. package/dist/api/agents.d.ts +4 -0
  12. package/dist/api/agents.d.ts.map +1 -0
  13. package/dist/api/agents.js +36 -0
  14. package/dist/api/agents.js.map +1 -0
  15. package/dist/api/apps.d.ts +6 -0
  16. package/dist/api/apps.d.ts.map +1 -0
  17. package/dist/api/apps.js +76 -0
  18. package/dist/api/apps.js.map +1 -0
  19. package/dist/api/auth.d.ts +9 -0
  20. package/dist/api/auth.d.ts.map +1 -0
  21. package/dist/api/auth.js +81 -0
  22. package/dist/api/auth.js.map +1 -0
  23. package/dist/api/builds.d.ts +14 -0
  24. package/dist/api/builds.d.ts.map +1 -0
  25. package/dist/api/builds.js +368 -0
  26. package/dist/api/builds.js.map +1 -0
  27. package/dist/api/comments.d.ts +5 -0
  28. package/dist/api/comments.d.ts.map +1 -0
  29. package/dist/api/comments.js +114 -0
  30. package/dist/api/comments.js.map +1 -0
  31. package/dist/api/invitations.d.ts +4 -0
  32. package/dist/api/invitations.d.ts.map +1 -0
  33. package/dist/api/invitations.js +95 -0
  34. package/dist/api/invitations.js.map +1 -0
  35. package/dist/api/passwordReset.d.ts +6 -0
  36. package/dist/api/passwordReset.d.ts.map +1 -0
  37. package/dist/api/passwordReset.js +64 -0
  38. package/dist/api/passwordReset.js.map +1 -0
  39. package/dist/api/profile.d.ts +3 -0
  40. package/dist/api/profile.d.ts.map +1 -0
  41. package/dist/api/profile.js +56 -0
  42. package/dist/api/profile.js.map +1 -0
  43. package/dist/api/recordings.d.ts +6 -0
  44. package/dist/api/recordings.d.ts.map +1 -0
  45. package/dist/api/recordings.js +167 -0
  46. package/dist/api/recordings.js.map +1 -0
  47. package/dist/api/settings.d.ts +4 -0
  48. package/dist/api/settings.d.ts.map +1 -0
  49. package/dist/api/settings.js +65 -0
  50. package/dist/api/settings.js.map +1 -0
  51. package/dist/api/team.d.ts +6 -0
  52. package/dist/api/team.d.ts.map +1 -0
  53. package/dist/api/team.js +65 -0
  54. package/dist/api/team.js.map +1 -0
  55. package/dist/api/tokens.d.ts +5 -0
  56. package/dist/api/tokens.d.ts.map +1 -0
  57. package/dist/api/tokens.js +39 -0
  58. package/dist/api/tokens.js.map +1 -0
  59. package/dist/db.d.ts +5 -0
  60. package/dist/db.d.ts.map +1 -0
  61. package/dist/db.js +43 -0
  62. package/dist/db.js.map +1 -0
  63. package/dist/index.d.ts +6 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +4 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/lib/config.d.ts +34 -0
  68. package/dist/lib/config.d.ts.map +1 -0
  69. package/dist/lib/config.js +97 -0
  70. package/dist/lib/config.js.map +1 -0
  71. package/dist/lib/mailer.d.ts +2 -0
  72. package/dist/lib/mailer.d.ts.map +1 -0
  73. package/dist/lib/mailer.js +30 -0
  74. package/dist/lib/mailer.js.map +1 -0
  75. package/dist/middleware/auth.d.ts +17 -0
  76. package/dist/middleware/auth.d.ts.map +1 -0
  77. package/dist/middleware/auth.js +65 -0
  78. package/dist/middleware/auth.js.map +1 -0
  79. package/dist/migrations/001_initial.sql +55 -0
  80. package/dist/migrations/002_dashboard_foundation.sql +54 -0
  81. package/dist/migrations/003_recordings.sql +14 -0
  82. package/dist/migrations/004_app_build_split.sql +88 -0
  83. package/dist/migrations/005_user_avatar.sql +1 -0
  84. package/dist/migrations/006_password_reset.sql +7 -0
  85. package/dist/migrations/007_app_platform_model.sql +34 -0
  86. package/dist/migrations/008_recordings_build_id.sql +4 -0
  87. package/dist/migrations/009_agent_resources.sql +12 -0
  88. package/dist/router.d.ts +16 -0
  89. package/dist/router.d.ts.map +1 -0
  90. package/dist/router.js +53 -0
  91. package/dist/router.js.map +1 -0
  92. package/dist/server.d.ts +2 -0
  93. package/dist/server.d.ts.map +1 -0
  94. package/dist/server.js +17 -0
  95. package/dist/server.js.map +1 -0
  96. package/dist/types.d.ts +41 -0
  97. package/dist/types.d.ts.map +1 -0
  98. package/dist/types.js +2 -0
  99. package/dist/types.js.map +1 -0
  100. package/package.json +72 -0
  101. package/public/assets/index-CIXHMEdP.css +1 -0
  102. package/public/assets/index-CzYNt1iH.js +499 -0
  103. package/public/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
  104. package/public/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
  105. package/public/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
  106. package/public/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
  107. package/public/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
  108. package/public/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
  109. package/public/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
  110. package/public/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
  111. package/public/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
  112. package/public/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
  113. package/public/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
  114. package/public/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
  115. package/public/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
  116. package/public/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
  117. package/public/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
  118. package/public/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
  119. package/public/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
  120. package/public/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
  121. package/public/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
  122. package/public/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
  123. package/public/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
  124. package/public/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
  125. package/public/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
  126. package/public/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
  127. package/public/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  128. package/public/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  129. package/public/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  130. package/public/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  131. package/public/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  132. package/public/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  133. package/public/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
  134. package/public/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
  135. package/public/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
  136. package/public/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
  137. package/public/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
  138. package/public/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
  139. package/public/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
  140. package/public/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
  141. package/public/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
  142. package/public/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
  143. package/public/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
  144. package/public/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
  145. package/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  146. package/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  147. package/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  148. package/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  149. package/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  150. package/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  151. package/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  152. package/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  153. package/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  154. package/public/favicon.svg +5 -0
  155. package/public/index.html +14 -0
  156. package/public/logo-dark.svg +12 -0
  157. package/public/logo.svg +12 -0
@@ -0,0 +1,368 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { tmpdir } from 'os';
4
+ import { randomUUID } from 'crypto';
5
+ import { spawnSync } from 'child_process';
6
+ import busboy from 'busboy';
7
+ import { getDb } from '../db.js';
8
+ import { requireAuth, verifyPat } from '../middleware/auth.js';
9
+ import { json, readJson } from '../router.js';
10
+ // ── zip / plist helpers ────────────────────────────────────────────────────
11
+ function parseXmlPlist(xml) {
12
+ const result = {};
13
+ const re = /<key>([^<]+)<\/key>\s*<string>([^<]*)<\/string>/g;
14
+ let m;
15
+ while ((m = re.exec(xml)) !== null)
16
+ result[m[1]] = m[2];
17
+ return result;
18
+ }
19
+ /** zip 내 루트 수준 *.app 디렉토리 이름을 찾는다. 없으면 null. */
20
+ function findAppDirInZip(zipPath) {
21
+ const list = spawnSync('unzip', ['-l', zipPath], { encoding: 'utf8' });
22
+ if (list.status !== 0)
23
+ return null;
24
+ // " 12345 2024-01-01 00:00:00 MyApp.app/" 형태를 매칭
25
+ const re = /\s(\S+\.app)\/\s*$/m;
26
+ const m = re.exec(list.stdout);
27
+ if (!m)
28
+ return null;
29
+ // 루트 레벨만 (슬래시 미포함)
30
+ const candidate = m[1];
31
+ return candidate.includes('/') ? null : candidate;
32
+ }
33
+ /** .app.zip에서 앱 메타데이터를 추출한다. 구조 오류 시 null. */
34
+ export function extractAppZipInfo(zipPath) {
35
+ const appDir = findAppDirInZip(zipPath);
36
+ if (!appDir)
37
+ return null;
38
+ const extract = spawnSync('unzip', ['-p', zipPath, `${appDir}/Info.plist`]);
39
+ if (extract.status !== 0 || !extract.stdout)
40
+ return null;
41
+ const plistBuf = extract.stdout;
42
+ let parsed;
43
+ if (plistBuf.subarray(0, 6).toString() === 'bplist') {
44
+ // binary plist — plutil 시도 (macOS only; 없으면 빈 결과)
45
+ const plutil = spawnSync('plutil', ['-convert', 'xml1', '-o', '-', '-'], {
46
+ input: plistBuf,
47
+ encoding: 'utf8',
48
+ });
49
+ if (plutil.status !== 0)
50
+ return { bundleId: null, versionName: null, buildNumber: null, appName: null };
51
+ parsed = parseXmlPlist(plutil.stdout);
52
+ }
53
+ else {
54
+ parsed = parseXmlPlist(plistBuf.toString('utf8'));
55
+ }
56
+ return {
57
+ bundleId: parsed['CFBundleIdentifier'] ?? null,
58
+ versionName: parsed['CFBundleShortVersionString'] ?? null,
59
+ buildNumber: parsed['CFBundleVersion'] ?? null,
60
+ appName: parsed['CFBundleDisplayName'] ?? parsed['CFBundleName'] ?? null,
61
+ };
62
+ }
63
+ /** ANDROID_HOME/build-tools 아래에서 최신 aapt 경로를 찾는다. macOS 기본 경로 + PATH fallback 포함. */
64
+ function findAapt() {
65
+ const candidates = [
66
+ process.env['ANDROID_HOME'],
67
+ `${process.env['HOME']}/Library/Android/sdk`,
68
+ ].filter(Boolean);
69
+ for (const androidHome of candidates) {
70
+ const buildToolsDir = path.join(androidHome, 'build-tools');
71
+ if (!fs.existsSync(buildToolsDir))
72
+ continue;
73
+ const versions = fs.readdirSync(buildToolsDir)
74
+ .filter(v => /^\d+\.\d+\.\d+$/.test(v))
75
+ .sort((a, b) => {
76
+ const p = (s) => s.split('.').map(Number);
77
+ const [am, ai, ap] = p(a);
78
+ const [bm, bi, bp] = p(b);
79
+ return bm - am || bi - ai || bp - ap;
80
+ });
81
+ for (const v of versions) {
82
+ const aapt = path.join(buildToolsDir, v, 'aapt');
83
+ if (fs.existsSync(aapt))
84
+ return aapt;
85
+ }
86
+ }
87
+ // PATH fallback — relay 프로세스가 ANDROID_HOME을 상속받지 못한 경우
88
+ const which = spawnSync('which', ['aapt'], { encoding: 'utf8' });
89
+ if (which.status === 0)
90
+ return which.stdout.trim();
91
+ return null;
92
+ }
93
+ /** aapt dump badging으로 APK 메타데이터를 추출한다. aapt 미설치 시 null 필드. */
94
+ function extractApkInfo(apkPath) {
95
+ const empty = { bundleId: null, versionName: null, buildNumber: null, appName: null };
96
+ const aapt = findAapt();
97
+ if (!aapt)
98
+ return empty;
99
+ const r = spawnSync(aapt, ['dump', 'badging', apkPath], { encoding: 'utf8' });
100
+ if (r.status !== 0)
101
+ return empty;
102
+ const out = r.stdout;
103
+ return {
104
+ bundleId: out.match(/package: name='([^']+)'/)?.[1] ?? null,
105
+ versionName: out.match(/versionName='([^']+)'/)?.[1] ?? null,
106
+ buildNumber: out.match(/versionCode='([^']+)'/)?.[1] ?? null,
107
+ appName: out.match(/application-label(?:-\w+)?:'([^']+)'/)?.[1] ?? null,
108
+ };
109
+ }
110
+ /**
111
+ * lipo로 시뮬레이터 슬라이스 존재를 확인한다.
112
+ * lipo 미설치(Linux relay) 시 null 반환 → 검증 skip.
113
+ */
114
+ function hasSimulatorSlice(zipPath, appDir) {
115
+ const binaryName = path.basename(appDir, '.app');
116
+ const extract = spawnSync('unzip', ['-p', zipPath, `${appDir}/${binaryName}`]);
117
+ if (extract.status !== 0 || !extract.stdout?.length)
118
+ return null;
119
+ const tmpBin = path.join(tmpdir(), `tapflow-lipo-${randomUUID()}`);
120
+ fs.writeFileSync(tmpBin, extract.stdout);
121
+ try {
122
+ const lipo = spawnSync('lipo', ['-info', tmpBin], { encoding: 'utf8' });
123
+ if (lipo.status !== 0)
124
+ return null; // lipo 없음 → skip
125
+ const out = lipo.stdout.toLowerCase();
126
+ // x86_64 = Intel 시뮬레이터, arm64 = Apple Silicon 시뮬레이터 또는 device
127
+ // arm64만 있는 경우는 구분 불가 → 통과 허용 (install 단계에서 실패 시 명확한 에러)
128
+ return out.includes('x86_64') || out.includes('arm64');
129
+ }
130
+ finally {
131
+ try {
132
+ fs.unlinkSync(tmpBin);
133
+ }
134
+ catch { /* ignore */ }
135
+ }
136
+ }
137
+ // ── app 자동 생성 / 조회 ──────────────────────────────────────────────────
138
+ export function upsertApp(name, bundleIdKey, platform) {
139
+ const db = getDb();
140
+ const existing = db.prepare('SELECT id, platform FROM apps WHERE bundle_id_key = ? LIMIT 1').get(bundleIdKey);
141
+ if (existing) {
142
+ // 다른 플랫폼 빌드가 올라오면 both로 업그레이드
143
+ if (existing.platform !== 'both' && existing.platform !== platform) {
144
+ db.prepare('UPDATE apps SET platform = ? WHERE id = ?').run('both', existing.id);
145
+ }
146
+ return existing.id;
147
+ }
148
+ const result = db.prepare('INSERT INTO apps (name, bundle_id_key, platform) VALUES (?, ?, ?)').run(name, bundleIdKey, platform);
149
+ return result.lastInsertRowid;
150
+ }
151
+ // ── handlers ──────────────────────────────────────────────────────────────
152
+ export function handleListBuilds(req, res) {
153
+ const auth = requireAuth(req, res);
154
+ if (!auth)
155
+ return;
156
+ const u = new URL(req.url ?? '/', 'http://x');
157
+ const page = Math.max(0, Number(u.searchParams.get('page') ?? 0));
158
+ const limit = Math.min(100, Math.max(1, Number(u.searchParams.get('limit') ?? 20)));
159
+ const q = u.searchParams.get('q') ?? '';
160
+ const platform = u.searchParams.get('platform') ?? '';
161
+ const status = u.searchParams.get('status') ?? '';
162
+ const appId = u.searchParams.get('app_id') ?? '';
163
+ const sortKey = ['uploaded_at', 'version_name', 'status_label'].includes(u.searchParams.get('sort') ?? '')
164
+ ? u.searchParams.get('sort') : 'uploaded_at';
165
+ const sortDir = u.searchParams.get('dir') === 'asc' ? 'ASC' : 'DESC';
166
+ const db = getDb();
167
+ const conds = [];
168
+ const params = [];
169
+ if (q) {
170
+ conds.push('b.version_name LIKE ?');
171
+ params.push(`%${q}%`);
172
+ }
173
+ if (platform) {
174
+ conds.push('ap.platform = ?');
175
+ params.push(platform);
176
+ }
177
+ if (status) {
178
+ conds.push('b.status_label = ?');
179
+ params.push(status);
180
+ }
181
+ if (appId) {
182
+ conds.push('b.app_id = ?');
183
+ params.push(Number(appId));
184
+ }
185
+ const where = conds.length ? `WHERE ${conds.join(' AND ')}` : '';
186
+ const baseFrom = `
187
+ FROM builds b
188
+ LEFT JOIN apps ap ON ap.id = b.app_id
189
+ LEFT JOIN users u ON u.id = b.uploader_id
190
+ `;
191
+ const total = db.prepare(`SELECT COUNT(*) as n ${baseFrom} ${where}`).get(...params).n;
192
+ const items = db.prepare(`
193
+ SELECT b.id, b.app_id, ap.name, b.version_name, b.build_number,
194
+ b.version_label, b.status_label, b.platform,
195
+ b.bundle_id, b.uploaded_at,
196
+ COALESCE(u.display_name, substr(u.email, 1, instr(u.email, '@') - 1)) as uploader
197
+ ${baseFrom}
198
+ ${where}
199
+ ORDER BY b.${sortKey} ${sortDir}
200
+ LIMIT ? OFFSET ?
201
+ `).all(...params, limit, page * limit);
202
+ json(res, 200, { items, total });
203
+ }
204
+ export function handleGetBuild(req, res, params) {
205
+ const auth = requireAuth(req, res);
206
+ if (!auth)
207
+ return;
208
+ const build = getDb().prepare(`
209
+ SELECT b.id, b.app_id, ap.name, b.version_name, b.build_number,
210
+ b.version_label, b.status_label, b.platform, b.bundle_id, b.uploaded_at
211
+ FROM builds b
212
+ LEFT JOIN apps ap ON ap.id = b.app_id
213
+ WHERE b.id = ?
214
+ `).get(params.id);
215
+ if (!build)
216
+ return json(res, 404, { error: 'Build not found' });
217
+ json(res, 200, build);
218
+ }
219
+ export async function handleUpdateBuild(req, res, params) {
220
+ const auth = requireAuth(req, res);
221
+ if (!auth)
222
+ return;
223
+ const body = await readJson(req);
224
+ const VALID_STATUS = ['Backlog', 'In Progress', 'Done', 'Rejected'];
225
+ const updates = [];
226
+ const values = [];
227
+ if ('status_label' in body) {
228
+ if (body.status_label !== null && !VALID_STATUS.includes(body.status_label ?? '')) {
229
+ return json(res, 400, { error: 'Invalid status_label' });
230
+ }
231
+ updates.push('status_label = ?');
232
+ values.push(body.status_label ?? null);
233
+ }
234
+ if ('version_label' in body) {
235
+ updates.push('version_label = ?');
236
+ values.push(body.version_label ?? null);
237
+ }
238
+ if (updates.length === 0)
239
+ return json(res, 400, { error: 'Nothing to update' });
240
+ const result = getDb().prepare(`UPDATE builds SET ${updates.join(', ')} WHERE id = ?`).run(...values, params.id);
241
+ if (result.changes === 0)
242
+ return json(res, 404, { error: 'Build not found' });
243
+ json(res, 200, { ok: true });
244
+ }
245
+ export function handleUploadBuild(req, res, uploadsDir) {
246
+ const pat = verifyPat(req);
247
+ const auth = pat ? { userId: pat.userId } : requireAuth(req, res);
248
+ if (!auth)
249
+ return;
250
+ if (pat && !pat.scope.includes('builds:write')) {
251
+ return json(res, 403, { error: 'Insufficient scope' });
252
+ }
253
+ const bb = busboy({ headers: req.headers, limits: { fileSize: 500 * 1024 * 1024 } });
254
+ const fields = {};
255
+ let savedPath = '';
256
+ let originalName = '';
257
+ let fileError = '';
258
+ let writePromise = Promise.resolve();
259
+ bb.on('field', (name, val) => { fields[name] = val; });
260
+ bb.on('file', (_field, stream, info) => {
261
+ originalName = info.filename;
262
+ const ext = path.extname(originalName).toLowerCase();
263
+ if (ext === '.ipa') {
264
+ fileError = 'iOS 시뮬레이터용 빌드는 .app.zip 형식이어야 합니다. xcodebuild -sdk iphonesimulator 로 빌드한 .app 디렉토리를 zip 압축해 업로드하세요.';
265
+ stream.resume();
266
+ return;
267
+ }
268
+ if (!['.zip', '.apk'].includes(ext)) {
269
+ fileError = 'Only .app.zip (iOS) or .apk (Android) files allowed';
270
+ stream.resume();
271
+ return;
272
+ }
273
+ const fileName = `${Date.now()}_${path.basename(originalName)}`;
274
+ savedPath = path.join(uploadsDir, 'builds', fileName);
275
+ fs.mkdirSync(path.dirname(savedPath), { recursive: true });
276
+ const ws = fs.createWriteStream(savedPath);
277
+ writePromise = new Promise((resolve, reject) => {
278
+ ws.on('finish', resolve);
279
+ ws.on('error', reject);
280
+ });
281
+ stream.pipe(ws);
282
+ });
283
+ bb.on('finish', async () => {
284
+ if (fileError)
285
+ return json(res, 400, { error: fileError });
286
+ if (!savedPath)
287
+ return json(res, 400, { error: 'File required' });
288
+ await writePromise;
289
+ const ext = path.extname(originalName).toLowerCase();
290
+ const isIos = ext === '.zip';
291
+ const platform = fields.platform ?? (isIos ? 'ios' : 'android');
292
+ const status = ['Backlog', 'In Progress', 'Done', 'Rejected'].includes(fields.status)
293
+ ? fields.status : null;
294
+ let bundleId = null;
295
+ let versionName = null;
296
+ let buildNumber = null;
297
+ let resolvedAppName = null;
298
+ if (isIos) {
299
+ const info = extractAppZipInfo(savedPath);
300
+ if (info === null) {
301
+ fs.unlinkSync(savedPath);
302
+ return json(res, 400, { error: 'zip 안에서 .app 디렉토리를 찾을 수 없습니다. .app 디렉토리를 포함한 zip 파일을 업로드하세요.' });
303
+ }
304
+ // lipo 슬라이스 검증 (macOS only; Linux에서는 null → skip)
305
+ const appDir = findAppDirInZip(savedPath);
306
+ if (appDir) {
307
+ const sliceOk = hasSimulatorSlice(savedPath, appDir);
308
+ if (sliceOk === false) {
309
+ fs.unlinkSync(savedPath);
310
+ return json(res, 400, { error: '디바이스용 슬라이스만 포함된 빌드입니다. xcodebuild -sdk iphonesimulator 로 빌드해 시뮬레이터 슬라이스를 포함하세요.' });
311
+ }
312
+ }
313
+ bundleId = info.bundleId;
314
+ versionName = info.versionName;
315
+ buildNumber = info.buildNumber;
316
+ resolvedAppName = info.appName;
317
+ }
318
+ else {
319
+ const info = extractApkInfo(savedPath);
320
+ bundleId = info.bundleId;
321
+ versionName = info.versionName;
322
+ buildNumber = info.buildNumber;
323
+ resolvedAppName = info.appName;
324
+ }
325
+ const bundleIdKey = bundleId ?? '__unknown__';
326
+ const appName = resolvedAppName ?? fields.label ?? path.basename(originalName, ext);
327
+ const db = getDb();
328
+ let appId;
329
+ if (fields.app_id) {
330
+ appId = Number(fields.app_id);
331
+ const app = db.prepare('SELECT id, bundle_id_key, platform FROM apps WHERE id = ?')
332
+ .get(appId);
333
+ if (!app) {
334
+ fs.unlinkSync(savedPath);
335
+ return json(res, 404, { error: 'App not found' });
336
+ }
337
+ // 추출된 bundle_id가 지정한 앱과 다르면 bundle_id 기준으로 앱을 찾거나 생성
338
+ if (bundleId && app.bundle_id_key && app.bundle_id_key !== bundleId) {
339
+ appId = upsertApp(appName, bundleIdKey, platform);
340
+ }
341
+ else {
342
+ if (app.platform !== 'both' && app.platform !== platform) {
343
+ db.prepare('UPDATE apps SET platform = ? WHERE id = ?').run('both', appId);
344
+ }
345
+ if (!app.bundle_id_key && bundleId) {
346
+ db.prepare('UPDATE apps SET bundle_id_key = ? WHERE id = ?').run(bundleId, appId);
347
+ }
348
+ }
349
+ }
350
+ else {
351
+ appId = upsertApp(appName, bundleIdKey, platform);
352
+ }
353
+ const result = db.prepare(`
354
+ INSERT INTO builds (app_id, version_name, build_number, bundle_id, status_label, file_path, label, version_label, uploader_id, platform)
355
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
356
+ `).run(appId, versionName, buildNumber, bundleId, status, savedPath, fields.label ?? null, fields.label ?? null, auth.userId, platform);
357
+ const build = db.prepare(`
358
+ SELECT b.id, b.app_id, ap.name, b.version_name, b.build_number,
359
+ b.bundle_id, b.status_label, b.platform, b.uploaded_at
360
+ FROM builds b LEFT JOIN apps ap ON ap.id = b.app_id
361
+ WHERE b.id = ?
362
+ `).get(result.lastInsertRowid);
363
+ json(res, 201, build);
364
+ });
365
+ bb.on('error', () => json(res, 500, { error: 'Upload failed' }));
366
+ req.pipe(bb);
367
+ }
368
+ //# sourceMappingURL=builds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builds.js","sourceRoot":"","sources":["../../src/api/builds.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import http from 'http';
2
+ export declare function handleListComments(req: http.IncomingMessage, res: http.ServerResponse): void;
3
+ export declare function handleCreateComment(req: http.IncomingMessage, res: http.ServerResponse, uploadsDir: string): void;
4
+ export declare function handleDeleteComment(req: http.IncomingMessage, res: http.ServerResponse, params: Record<string, string>): void;
5
+ //# sourceMappingURL=comments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../../src/api/comments.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAQvB,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CA0C5F;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,UAAU,EAAE,MAAM,GACjB,IAAI,CA4DN;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,IAAI,CAeN"}
@@ -0,0 +1,114 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import busboy from 'busboy';
4
+ import { getDb } from '../db.js';
5
+ import { requireAuth } from '../middleware/auth.js';
6
+ import { json } from '../router.js';
7
+ export function handleListComments(req, res) {
8
+ const auth = requireAuth(req, res);
9
+ if (!auth)
10
+ return;
11
+ const buildId = new URL(req.url ?? '/', 'http://x').searchParams.get('build_id');
12
+ if (!buildId)
13
+ return json(res, 400, { error: 'build_id required' });
14
+ const db = getDb();
15
+ const comments = db.prepare(`
16
+ SELECT c.id, c.body, c.created_at,
17
+ COALESCE(u.display_name, substr(u.email, 1, instr(u.email, '@') - 1)) as author,
18
+ u.avatar_url as authorAvatarUrl
19
+ FROM comments c
20
+ JOIN users u ON u.id = c.author_id
21
+ WHERE c.build_id = ?
22
+ ORDER BY c.created_at ASC
23
+ `).all(buildId);
24
+ const attachments = db.prepare(`
25
+ SELECT ca.id, ca.comment_id, ca.file_path, ca.mime
26
+ FROM comment_attachments ca
27
+ JOIN comments c ON c.id = ca.comment_id
28
+ WHERE c.build_id = ?
29
+ `).all(buildId);
30
+ const attachMap = new Map();
31
+ for (const a of attachments) {
32
+ if (!attachMap.has(a.comment_id))
33
+ attachMap.set(a.comment_id, []);
34
+ attachMap.get(a.comment_id).push(a);
35
+ }
36
+ const result = comments
37
+ .map((c) => ({
38
+ ...c,
39
+ attachments: (attachMap.get(c.id) ?? []).map((a) => ({
40
+ id: a.id,
41
+ file_path: `/uploads/comments/${path.basename(a.file_path)}`,
42
+ mime: a.mime,
43
+ })),
44
+ }));
45
+ json(res, 200, result);
46
+ }
47
+ export function handleCreateComment(req, res, uploadsDir) {
48
+ const auth = requireAuth(req, res);
49
+ if (!auth)
50
+ return;
51
+ const bb = busboy({ headers: req.headers, limits: { fileSize: 5 * 1024 * 1024 } });
52
+ const fields = {};
53
+ let attachmentPath = '';
54
+ let attachmentMime = '';
55
+ let sizeError = false;
56
+ bb.on('field', (name, val) => { fields[name] = val; });
57
+ bb.on('file', (_field, stream, info) => {
58
+ const allowed = ['image/png', 'image/jpeg', 'image/webp'];
59
+ if (!allowed.includes(info.mimeType)) {
60
+ stream.resume();
61
+ return;
62
+ }
63
+ const ext = { 'image/png': '.png', 'image/jpeg': '.jpg', 'image/webp': '.webp' }[info.mimeType];
64
+ const fileName = `${Date.now()}${ext}`;
65
+ attachmentPath = path.join(uploadsDir, 'comments', fileName);
66
+ attachmentMime = info.mimeType;
67
+ fs.mkdirSync(path.dirname(attachmentPath), { recursive: true });
68
+ let size = 0;
69
+ stream.on('data', (chunk) => {
70
+ size += chunk.length;
71
+ if (size > 5 * 1024 * 1024)
72
+ sizeError = true;
73
+ });
74
+ stream.pipe(fs.createWriteStream(attachmentPath));
75
+ });
76
+ bb.on('finish', () => {
77
+ if (sizeError)
78
+ return json(res, 400, { error: 'Max 5MB per attachment' });
79
+ if (!fields.build_id || !fields.body?.trim()) {
80
+ return json(res, 400, { error: 'build_id and body required' });
81
+ }
82
+ const db = getDb();
83
+ const commentResult = db.prepare("INSERT INTO comments (build_id, author_id, body, created_at) VALUES (?, ?, ?, strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))").run(fields.build_id, auth.userId, fields.body.trim());
84
+ const commentId = commentResult.lastInsertRowid;
85
+ if (attachmentPath) {
86
+ const size = fs.statSync(attachmentPath).size;
87
+ db.prepare('INSERT INTO comment_attachments (comment_id, file_path, mime, size) VALUES (?, ?, ?, ?)').run(commentId, attachmentPath, attachmentMime, size);
88
+ }
89
+ const comment = db.prepare(`
90
+ SELECT c.id, c.body, c.created_at, u.display_name as author
91
+ FROM comments c JOIN users u ON u.id = c.author_id
92
+ WHERE c.id = ?
93
+ `).get(commentId);
94
+ json(res, 201, comment);
95
+ });
96
+ bb.on('error', () => json(res, 500, { error: 'Upload failed' }));
97
+ req.pipe(bb);
98
+ }
99
+ export function handleDeleteComment(req, res, params) {
100
+ const auth = requireAuth(req, res);
101
+ if (!auth)
102
+ return;
103
+ const db = getDb();
104
+ const comment = db.prepare('SELECT author_id FROM comments WHERE id = ?').get(params.id);
105
+ if (!comment)
106
+ return json(res, 404, { error: 'Comment not found' });
107
+ const user = db.prepare('SELECT role FROM users WHERE id = ?').get(auth.userId);
108
+ if (comment.author_id !== auth.userId && user.role !== 'Admin') {
109
+ return json(res, 403, { error: 'Forbidden' });
110
+ }
111
+ db.prepare('DELETE FROM comments WHERE id = ?').run(params.id);
112
+ json(res, 204, null);
113
+ }
114
+ //# sourceMappingURL=comments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments.js","sourceRoot":"","sources":["../../src/api/comments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import http from 'http';
2
+ export declare function handleVerify(req: http.IncomingMessage, res: http.ServerResponse): void;
3
+ export declare function handleAccept(req: http.IncomingMessage, res: http.ServerResponse, uploadsDir: string): void;
4
+ //# sourceMappingURL=invitations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../src/api/invitations.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAUvB,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAYtF;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,UAAU,EAAE,MAAM,GACjB,IAAI,CA4EN"}
@@ -0,0 +1,95 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import crypto from 'crypto';
4
+ import busboy from 'busboy';
5
+ import { getDb } from '../db.js';
6
+ import { signJwt } from '../middleware/auth.js';
7
+ import { makePasswordHash } from './auth.js';
8
+ import { json } from '../router.js';
9
+ export function handleVerify(req, res) {
10
+ const token = new URL(req.url ?? '/', 'http://x').searchParams.get('token');
11
+ if (!token)
12
+ return json(res, 400, { error: 'token required' });
13
+ const db = getDb();
14
+ const inv = db.prepare(`
15
+ SELECT role FROM invitations
16
+ WHERE token = ? AND used_at IS NULL AND expires_at > datetime('now')
17
+ `).get(token);
18
+ if (!inv)
19
+ return json(res, 410, { error: 'Invitation expired or not found' });
20
+ json(res, 200, { role: inv.role });
21
+ }
22
+ export function handleAccept(req, res, uploadsDir) {
23
+ const bb = busboy({ headers: req.headers, limits: { fileSize: 2 * 1024 * 1024 } });
24
+ const fields = {};
25
+ let fileBuffer = null;
26
+ let fileMime = '';
27
+ let sizeError = false;
28
+ bb.on('field', (name, val) => { fields[name] = val; });
29
+ bb.on('file', (_field, stream, info) => {
30
+ const allowed = ['image/png', 'image/jpeg'];
31
+ if (!allowed.includes(info.mimeType)) {
32
+ stream.resume();
33
+ return;
34
+ }
35
+ fileMime = info.mimeType;
36
+ const chunks = [];
37
+ let size = 0;
38
+ stream.on('data', (c) => {
39
+ size += c.length;
40
+ if (size <= 2 * 1024 * 1024)
41
+ chunks.push(c);
42
+ else
43
+ sizeError = true;
44
+ });
45
+ stream.on('end', () => { if (!sizeError)
46
+ fileBuffer = Buffer.concat(chunks); });
47
+ });
48
+ bb.on('finish', () => {
49
+ if (sizeError)
50
+ return json(res, 400, { error: 'Max 2MB for avatar' });
51
+ const { token, password } = fields;
52
+ if (!token || !password)
53
+ return json(res, 400, { error: 'token and password required' });
54
+ if (password.length < 8)
55
+ return json(res, 400, { error: 'Password must be at least 8 characters' });
56
+ const db = getDb();
57
+ const inv = db.prepare(`
58
+ SELECT id, email, role FROM invitations
59
+ WHERE token = ? AND used_at IS NULL AND expires_at > datetime('now')
60
+ `).get(token);
61
+ if (!inv)
62
+ return json(res, 410, { error: 'Invitation expired or not found' });
63
+ const passwordHash = makePasswordHash(password);
64
+ const displayName = fields.display_name?.trim() || (inv.email?.split('@')[0] ?? 'User');
65
+ let userId;
66
+ const existing = inv.email
67
+ ? db.prepare('SELECT id FROM users WHERE email = ?').get(inv.email)
68
+ : undefined;
69
+ if (existing) {
70
+ db.prepare('UPDATE users SET password_hash = ?, role = ?, display_name = ? WHERE id = ?')
71
+ .run(passwordHash, inv.role, displayName, existing.id);
72
+ userId = existing.id;
73
+ }
74
+ else {
75
+ const result = db.prepare('INSERT INTO users (email, role, password_hash, display_name) VALUES (?, ?, ?, ?)').run(inv.email ?? `user_${crypto.randomBytes(4).toString('hex')}@tapflow.local`, inv.role, passwordHash, displayName);
76
+ userId = result.lastInsertRowid;
77
+ }
78
+ if (fileBuffer && fileMime) {
79
+ const ext = fileMime === 'image/png' ? '.png' : '.jpg';
80
+ const avatarPath = path.join(uploadsDir, 'avatars', `user-${userId}${ext}`);
81
+ fs.mkdirSync(path.dirname(avatarPath), { recursive: true });
82
+ fs.writeFileSync(avatarPath, fileBuffer);
83
+ db.prepare('UPDATE users SET avatar_url = ? WHERE id = ?').run(`/uploads/avatars/user-${userId}${ext}`, userId);
84
+ }
85
+ db.prepare("UPDATE invitations SET used_at = datetime('now') WHERE id = ?").run(inv.id);
86
+ const user = db.prepare('SELECT email, role FROM users WHERE id = ?').get(userId);
87
+ const jwtToken = signJwt({ userId, email: user.email, role: user.role });
88
+ const cookie = `tapflow_token=${jwtToken}; HttpOnly; Path=/; SameSite=Lax; Max-Age=${7 * 24 * 3600}`;
89
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': cookie });
90
+ res.end(JSON.stringify({ ok: true }));
91
+ });
92
+ bb.on('error', () => json(res, 500, { error: 'Failed to process request' }));
93
+ req.pipe(bb);
94
+ }
95
+ //# sourceMappingURL=invitations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invitations.js","sourceRoot":"","sources":["../../src/api/invitations.ts"],"names":[],"mappings":"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"}