Skip to content

Streaming, large uploads through the edge, Files Explorer (buckets, folders, tag folders), demo data opt-in, English UI - #7

Open
hacka0wi wants to merge 5 commits into
mainfrom
feat/prod-readiness
Open

hacka0wi wants to merge 5 commits into
mainfrom
feat/prod-readiness

Conversation

@hacka0wi

Copy link
Copy Markdown
Member

แก้แกนหลักของ #2 (ไฟล์ใหญ่และ streaming) และบางข้อของ #1 — ทดสอบที่ local ครบแล้ว ยังไม่ได้ขึ้น UAT

ทำอะไร

  • storage แบบ stream: ObjectStore เพิ่ม size / open_range / put_path / rename (default ใช้ของเดิม, fs override แบบ seek+stream)
  • เข้ารหัส v2 ทีละ chunk 64 KiB: STREAM construction (nonce = prefix | last | index, AAD = header) — Range ถอดรหัสเฉพาะ chunk ที่ใช้ · ตรวจจับการแก้/ตัดท้ายได้ · ไฟล์ v1 เดิมยังอ่านได้ (แยกด้วย magic 8 byte, ไม่แก้ schema)
  • ดาวน์โหลด (serve.rs): stream + Range + If-Range + 416 ทุกเส้น (download, share, preview PDF, /FileService/downloadFile|previewFile) · proxy ไปฮับเดิมเป็น stream + ส่ง Range ต่อ + client ร่วมพร้อม timeout
  • อัปโหลด: multipart ทุกเส้น + tus finalize stream ลงไฟล์พัก → เช็ค quota → commit · UPLOAD_MAX_MB (ค่าเริ่ม 2048) · เกินเพดานตอบ 413
  • ขอบนอกที่รับแค่ GET/POST และ body ≤ 100 MB: tus ใช้ overridePatchMethod + ส่ง HEAD ผ่าน override · resume หลัง reload
  • P1: พร้อมรับ workload ระดับ production (rate limit, งานหนักใน async, rotation, backup, probe) #1: rate limit ของเส้น /FileService นับแยกตามระบบต้นทาง (LEGACY_UPLOAD_RATE_PER_MIN, ค่าเริ่ม 1200) · thumbnail/text ออกจาก async runtime + ทำเบื้องหลัง (P1_MAX_MB) · เลิกเก็บสำเนา PDF ใน file_previews · rename ไม่เขียนไฟล์ใหม่ · ย้ายข้าม bucket เช็ค quota · share download ไม่ panic กับชื่อไทย

ผลวัด (local, release build, เข้ารหัสเปิด)

ก่อน หลัง
ดาวน์โหลด 250 MB — TTFB / RSS 2.4 s / 511 MB 0.025 s / 14 MB
ดาวน์โหลด 250 MB ทั้งไฟล์ 2.5 s 2.07 s
Range 1 MB ได้ทั้งไฟล์ (200) 206, 12–30 ms
250 MB ×3 พร้อมกัน — RSS 1,117 MB 15 MB
อัปโหลด 1 GB — RSS ~5.7 GB (ประมาณจาก 300 MB = 1.7 GB) 13 MB
1 GB ×4 พร้อมกัน — RSS – 32 MB
proxy ฮับเดิม 300 MB — RSS ทั้งไฟล์ 13 MB
อัปโหลดติดกัน 150 ครั้ง (เส้นเดิม) ผ่าน 58 ผ่าน 150
tus 300 MB ผ่านตัวจำลองขอบนอก PATCH โดน 403 ผ่าน · ตัดที่ 120 MB แล้ว resume จบ · MD5 ตรง

ทดสอบ

  • unit 34/34 (เพิ่ม: range ทุกขอบ chunk ทั้งเข้ารหัส/ไม่เข้ารหัส, ไฟล์ว่าง, tamper/truncation, อ่าน v1)
  • integration tests/api.rs 42/42 บนฐานว่าง
  • frontend tsc + next build + next lint ผ่าน

ยังไม่ได้ทำ (ต่อใน #2)

  • job แปลงไฟล์ v1 → v2 (ไฟล์เก่ายังอ่านแบบทั้งก้อน)
  • S3 store: Range GET/multipart upload
  • tus: row lock ตอน PATCH, Tus-Max-Size, quota ตอนสร้าง session

hacka0wi and others added 4 commits September 25, 2026 00:14
…nge ทุกเส้น

เดิมทุกเส้นอ่านทั้งไฟล์ลงหน่วยความจำ (อัปโหลด ~5.7 เท่าของขนาดไฟล์, ดาวน์โหลด
250 MB ×3 = 1.1 GB) และเส้น /FileService ไม่สน Range (ขอ 1 MB ได้ทั้งไฟล์)
ทำให้วิดีโอเลื่อนเวลาไม่ได้และไฟล์ใหญ่ทำ pod ล่ม — refs #2

storage
- ObjectStore: size / open_range / put_path / rename / staging_dir
  (default ใช้ของเดิม, fs override แบบ seek+stream และ rename)
- encryption format v2: AES-256-GCM ทีละ 64 KiB (STREAM: nonce = prefix|last|index,
  AAD = header) · อ่านช่วงไหนถอดรหัสเฉพาะ chunk นั้น · จับแก้/ตัดท้ายได้
- ไฟล์ v1 เดิมยังอ่านได้ (แยกด้วย magic 8 byte) — ไม่ต้องแก้ schema
- StageWriter: เขียนไฟล์พัก + เข้ารหัส + MD5 ระหว่างทาง · sweep ของค้างตอน start

ดาวน์โหลด (serve.rs)
- stream + Range + If-Range + 416 ทุกเส้น: /fh download, share, preview PDF,
  /FileService/downloadFile|previewFile
- proxy ไปฮับเดิม: stream, ส่ง Range/If-Range ต่อ, คืน 206/Content-Range,
  client ร่วมพร้อม timeout, url-encode fileId
- share download ไม่ panic กับชื่อไฟล์ภาษาไทยแล้ว (RFC 5987)

อัปโหลด
- multipart ทุกเส้น (single, batch, /FileService) stream ลงไฟล์พัก แล้วเช็ค quota
  ก่อน commit · tus finalize stream จากไฟล์ temp · UPLOAD_MAX_MB (ค่าเริ่ม 2048)
- เกินเพดานตอบ 413 ชัดเจน (เดิม 400 "Error parsing multipart")
- P1 (ข้อความ/thumbnail/preview) ทำเบื้องหลัง ข้ามไฟล์ > P1_MAX_MB (32) ·
  thumbnail/text ออกจาก async runtime · เลิกเก็บสำเนา PDF ใน file_previews
- rename ไม่เขียนไฟล์ใหม่ · ย้ายข้าม bucket ใช้ rename + เช็ค quota ของ bucket ปลายทาง

rate limit
- เส้น /FileService ที่ใช้บัญชีบริการร่วม นับแยกตามระบบต้นทาง
  LEGACY_UPLOAD_RATE_PER_MIN (ค่าเริ่ม 1200) — เดิมทั้ง NEB รวมกันได้ 60/นาที/pod

วัดที่ local (release, เข้ารหัสเปิด)
| | ก่อน | หลัง |
| ดาวน์โหลด 250 MB TTFB / RSS | 2.4 s / 511 MB | 0.025 s / 14 MB |
| Range 1 MB | ได้ทั้งไฟล์ | 206, 12–30 ms |
| 250 MB ×3 พร้อมกัน RSS | 1,117 MB | 15 MB |
| อัปโหลด 1 GB RSS | ~5.7 GB (ประมาณ) | 13 MB |
| 1 GB ×4 พร้อมกัน RSS | – | 32 MB |
| proxy ฮับเดิม 300 MB RSS | ทั้งไฟล์ | 13 MB |
| อัปโหลด 150 ครั้ง | ผ่าน 58 | ผ่าน 150 |
tests: unit 34/34 · integration (tests/api.rs) 42/42 บนฐานว่าง

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esume ต่อจากจุดเดิม

- tus: overridePatchMethod (PATCH → POST + X-HTTP-Method-Override) และ httpStack
  ที่ส่ง HEAD เป็น POST + override (ใช้ตอน resume/retry) · chunk 8 MB
- resume หลัง reload/เน็ตหลุด ด้วย findPreviousUploads/resumeFromPreviousUpload
- backend method_override รับ HEAD เพิ่ม

พิสูจน์กับตัวจำลองขอบนอก (403 เมธอดอื่น, 413 body > 100 MB):
- tus แบบเดิม: PATCH โดน 403
- แบบใหม่: 300 MB ผ่าน · ตัดกลางทางที่ 120 MB แล้ว resume จบ · MD5 ตรง
- Range 206 ผ่านขอบนอก ไบต์ตรงกับต้นฉบับ
refs #2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ษาไทยและเห็นถังเก็บไฟล์ชัด

ข้อมูลตัวอย่าง (refs #6)
- บัญชีตัวอย่าง (admin@acme.go.th / admin123 ฯลฯ) และแถว demo (comment, workflow,
  notification, version) สร้างเฉพาะเมื่อ DEMO_MODE=1 — ติดตั้งจริงเริ่มจากฐานว่าง
- PURGE_DEMO_DATA=1 ล้างข้อมูลตัวอย่างที่เวอร์ชันก่อนสร้างไว้ (ไฟล์/มุมมอง/ระบบ/หน่วยงาน/
  โฟลเดอร์ demo) — ลบระบบ/หน่วยงาน/โฟลเดอร์เฉพาะที่ไม่มีไฟล์จริงค้าง, บัญชี demo ถูกปิด ไม่ลบ
- CI ตั้ง DEMO_MODE=1 เพราะ integration test ใช้บัญชีตัวอย่าง · README/CLAUDE.md อัปเดต

แถบเมนูซ้าย
- ป้ายภาษาไทยทั้งหมด · ปุ่ม "อัปโหลดไฟล์" เด่นด้านบน
- หัวข้อ "ถังเก็บไฟล์ (n)" แทน "ระบบต้นทาง" · แต่ละถังบอก "n ไฟล์"/"ว่าง" · tooltip ชื่อ bucket + ขนาด
  · ลิงก์ "จัดการ" ไปหน้าตั้งค่า · ไม่แสดงถังส่วนตัวซ้ำ (มีลิงก์ของตัวเองแล้ว)
- หน่วยงานใต้ถังที่เลือกมีหัวข้อกำกับ · มุมมองที่บันทึกพับเก็บไว้ท้ายรายการ

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… search, group by — UI in English

Users found the file hub hard to follow: no workspace → bucket → folder picture,
raw ids in filter chips ("system_id = sys_personal_…"), bucket/folder creation
buried in settings, no way to group files by tag, and mixed Thai/English labels.

Explorer (/explorer, now the "Files" entry)
- left tree: Workspace › Buckets (file counts) › nested folders, My Drive,
  Tag folders — every tag grouped by key (fiscal-year › 2569 …) plus saved
  tag folders (name + tags, match all/any; stored as views, layout "tagfolder")
- breadcrumb, New bucket (admin) / New folder / Upload here in context
- bucket cards at workspace root (files, size, quota bar)
- search scoped to where you are ("Search in Approved…"), results show Location
- Group by: file type, status, owner, month, or any tag key
- readable chips ("Search: “0142”", "Grouped by tag “agency”")

Backend
- GET /api/tags (tag, key, value, count; scoped to visible buckets)
- /api/files: ?tag= (exact tag) and ?q= (name/tag substring)
- DELETE /api/views/:id (creator or admin)
- auto tags use English keys: system:, org:, fiscal-year:, uploaded-by:,
  via:legacy-fileservice, type:, group:
- bucket id validated (a-z 0-9 -, 2–63); paused buckets refuse new files

UI language: English throughout (sidebar, dashboard, file page, login,
access denied, bucket settings — quota in MB/GB/TB with usage bar)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hacka0wi hacka0wi changed the title Streaming + เข้ารหัสทีละ chunk + Range ทุกเส้น + อัปโหลดไฟล์ใหญ่ผ่านขอบนอก (#2) Streaming, large uploads through the edge, Files Explorer (buckets, folders, tag folders), demo data opt-in, English UI Sep 24, 2026
@hacka0wi

Copy link
Copy Markdown
Member Author

Added two commits:

Tests: unit 34/34, integration 42/42 (fresh DB, DEMO_MODE=1), next build/lint/tsc OK.

FileHub is a shared product, but earlier work hard-coded one customer into it
(organisation name and e-mail domain on the login page, portal colours in CSS,
portal link and identity-system name on the access-denied page, a Thai
Buddhist-era fiscal year starting in October, customer examples in forms,
and Thai / customer-specific comments throughout the source).

- migration 0017: workspace_config keys org_name, brand_accent,
  login_email_placeholder, portal_url, portal_label, access_help,
  fiscal_year_start_month (0 = off), fiscal_year_era (CE|BE); neutral defaults
- GET /api/branding (public, presentation keys only; accent validated #rrggbb)
- root layout: title from branding, accent injected as CSS variables
  (derived shades via color-mix); tokens.css back to the product default
- login + access-denied pages read branding; portal link hidden when unset
- fiscal-year auto tag follows the workspace setting (off by default)
- Settings → General → Branding card to edit all of the above
- neutral form examples; source comments and log/error messages in English
  without customer references; legacy import tags in English

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant