Reads application version from package.json via env.ts readPackageVersion()
and returns it alongside status so deploy verification is instant:
curl https://upload.asepharyana.my.id/health -> {status: ok, version: X.Y.Z}
- .releaserc.json: commit-analyzer + release-notes-generator + changelog
+ exec (prepare) + git + github plugins.
- .semrel/prepare.mjs: syncs next release version into package.json AND
flake.nix before the release commit — guarantees a fresh Nix store path
on every deploy (fixes the trap where a source change without a version
bump reused the same store path and CI silently deployed stale code).
- release.yml: runs semantic-release on main push (GITHUB_TOKEN,
isolated /tmp/sr-tools install so package.json/bun.lock stay clean).
- Release commit (no [skip ci]) triggers deploy.yml → auto build+deploy.
- Baseline tag v1.1.1 backfilled at current HEAD.
Force a fresh Nix store path so the Buffer-from-gzip fix actually reaches
the VPS. The 1.1.0 store path hash was reused by Nix (identical derivation
inputs from Nix's perspective), so a version bump guarantees the fixed
dist bundle is copied and the profile is switched.
Root cause of '400: Bad Request: there is no document in the request' on
chunked uploads of compressible files (MP4, zip, text, etc.):
Bun.gzipSync() returns a plain Uint8Array, NOT a Buffer. Telegraf
(sendDocument/sendVideo) only recognises Buffer/Blob/stream sources as
file uploads — a plain Uint8Array produces an empty multipart body and
Telegram rejects the request.
Verified via isolated forwardToStorage probes:
- raw Buffer chunk (.bin, MP4) -> OK
- plain Uint8Array chunk -> FAIL: there is no document
- Buffer.from(gzip) chunk -> OK (after fix)
- .gz extension on Uint8Array did NOT help (rules out MIME/extension)
This only affected chunked uploads of compressible files; incompressible
files (random .bin) worked by accident because gzip didn't shrink them so
the raw Buffer was forwarded unchanged.
Fixes browser CORS error when frontend fetches audio via Web Audio API
(decodeAudio): the 302 redirected to api.telegram.org which lacks
Access-Control-Allow-Origin, blocking fetch/XHR. Now the file bytes are
proxied server-side with CORS headers + proper Content-Type, so the
browser stays same-origin. Also adds CORS to chunked/archive streams.
Bun 1.4.0 (upgraded 0deb607) breaks Telegraf 4.15 sendDocument with
'The socket connection was closed unexpectedly' — reproduced with the same
libraries: Telegraf 4.15.0 on Bun 1.4.0 fails exactly like prod, on Bun 1.3.14
it returns a proper HTTP response. All uploads 500 since the upgrade deployed
(Aug 29 11:06). Pin flake nix override + CI bun-version to 1.3.14.
- Update Nix flake.nix overlay to use Bun 1.4.0 (overrideAttrs)
- Update CI (setup-bun) to pin bun-version: 1.4.0
- Update package.json packageManager/Dockerfile/vercel.json
- sha256: sha256:Poy0vf7yJ/hzk33QiQj5gnshI5Q7dfbaMD7xgwiyDKw=
Add comprehensive unit tests and repair stale tests that referenced the
old (pre-refactor) src/utils/* layout which no longer exists:
- s3-range: expand to 25 cases (suffix, clamping, malformed, zero-size,
invalid totals, content-range formatting)
- s3-object-stream: rewrite against the real interfaces/s3 module; add
multi-part ordering, ranges spanning parts, S3/CORS headers, fetch-error
propagation
- s3-helpers-edge (new): compress heuristics, virtual-host bucket parsing,
S3 route detection, client-IP/trustProxy, S3 response headers
- s3-auth-edge (new): verifyBodyHash, isS3Request, canonical-query-string
encoding/sorting
- chunked-storage: rewrite against the real ChunkedStorage class (was
importing deleted src/utils/chunked-storage) — chunk split, hashing,
compression, size-limit guards, forwarding
- zip: fix stale import + add path-traversal/duplicate sanitization,
locateZipEntry, empty-name fallback
- s3-docker-registry: fix stale src/config import; correct the rate-limit
test to assert S3 routes INTENTIONALLY bypass rate limiting
- temp-stream (new): streamToTemp hashing, MD5, signature bytes, empty and
oversized streams
- package.json: add the S3/unit files to test and test:s3 scripts
All new unit tests pass when run per-file (the project's documented mode to
avoid cross-file mock pollution). s3-sdk.test.ts (live E2E against a running
server) is deliberately excluded from test:s3.
Optimize the S3 GET path for chunked/multipart objects and reduce Telegram
API round-trips:
- object-stream: fetch object parts concurrently (bounded, in-order fan-in)
instead of serializing N sequential Telegram CDN fetches. Response latency
is now ~the slowest part fetch, not the sum of all part fetches.
- bot-pool.getFileInfo: cache file_id -> file_path in the existing in-memory
cache so repeated S3 GET/HEAD of the same object skip the Telegram API call
(file-controller had its own cache wrapper; the S3 path did not).
- chunked-storage + s3-controller: resolve multipart/chunked part CDN URLs
concurrently via Promise.all instead of sequentially.
- s3-controller multipart: await writer.end() before re-reading the temp part
file to avoid a flush race.
Adds object-stream-parallel.test.ts covering in-order fan-in, byte ranges,
and single-part responses even when the slowest part resolves out of order.
The 1GB suite downloads a 1GB fixture from Hetzner and uploads it to the
live deployment. Bun's Response-body write hangs against this source, so
the suite burned the full hook timeout on every routine run. Now:
- skipped by default (opt-in via RUN_LARGE_E2E=1)
- pre-flight Range probe (8s AbortSignal) fails fast when source is down
- 10min hook timeout retained for when the suite is actually opted in
The Login button (onclick=window.showAuthScreen()) was dead — showAuthScreen
was defined but never exported via Object.assign. Also drop dead code from
the e2e suite (unused createReadStream import, unused s3StreamRequest).
Build & Deploy (Nix) / build-and-deploy (push) Successful in 54s
handleHome looked up `${import.meta.dir}/home.html` which exists in neither
layout: dev (src/interfaces/http/controllers/) nor prod bundle
($out/share/teleuploader/dist/ — flake copies home.html beside dist/).
Add resolveHomeHtml(): walk up from import.meta.dir (bounded) to find
home.html. Works for dev (src/home.html, 4 levels up) and prod
(../home.html, 1 level up). Fails fast with a clear error instead of a
bare ENOENT 500. Tests cover both layouts + not-found fallback.
Build & Deploy (Nix) / build-and-deploy (push) Successful in 56s
Chunk parts > 19MB are stored to Telegram but getFile cannot resolve files over 20MB ('Bad Request: file is too big'), making every part undownloadable (prod bug 2026-08-01: 48MB chunk -> download 500).
- src/env.ts: reject TELEGRAM_CHUNK_SIZE_BYTES > 19922944 at startup (log error + throw), default changed 20MB -> 19MB
- src/shared/utils/validation.ts: TELEGRAM_CHUNK_SIZE_MAX_BYTES constant; asSafeChunkSize now enforces the max at runtime (covers S3 multipart parts too)
- test/env.test.ts: unit tests + subprocess fail-fast tests (48MB rejected, 19MB accepted)
- test/helpers/setup-env.ts: pin safe chunk size so a stale .env can't break the suite
- .env.example + CLAUDE.md: document the 20MB getFile limit
feat: add S3 bucket versioning support and related XML response handling
refactor: rename temporary file paths from 'teleuploader' to 'filedrop' for consistency
fix: update Swagger documentation to reflect new API name and descriptions
test: add unit tests for authentication routes and utilities
test: implement end-to-end tests for S3 bucket configuration and versioning
chore: update environment variable defaults for new service name
Wire createGetObjectResponse into single-part GetObject handler for
proper Range/Content-Range support (200, 206, 416). Update presigned
E2E test to require 200. Add SDK Range request test.
- Apply Biome organize-import/formatting fixes across changed S3 files
- Replace remaining string concatenations with template literals for lint
- Make home page inline handlers explicit via window.* and add button types
- Clean S3 auth lint issues with dot-property access and optional chaining
- Keep GetObject proxy and production/S3 SDK tests passing
Verification:
- bun run lint (0 errors, 1 CSS specificity warning)
- S3_SECRET_KEY=<env> bun test test/production-e2e.test.ts (29 pass)
- S3_SECRET_KEY=<env> bun test test/s3-sdk.test.ts (20 pass)
- bun test test/s3-auth.test.ts (5 pass)
- Add proxyS3Get config (PROXY_S3_GET env, default true) to env.ts
- Proxy handleGetObject and handleGetMultipartObject: fetch from Telegram
CDN and return 200 with streaming body instead of 302 redirect
- Real S3 clients (AWS SDK v3) expect 200+body on GetObject, not redirect
- Legacy 302 redirect path preserved when proxyS3Get=false
- Updated production-e2e: GetObject asserts 200 with body content
- Updated s3-sdk.test.ts: GetObject asserts 200 with body (removed try/catch)
- Cleaned up unused multipart imports in s3-sdk.test.ts
All 49 tests pass (29 production-e2e + 20 s3-sdk).
Tests use @aws-sdk/client-s3 against https://upload.asepharyana.my.id
with forcePathStyle=true. Covers all standard S3 operations:
ListBuckets, CreateBucket, HeadBucket, DeleteBucket, PutObject,
GetObject, HeadObject, ListObjectsV1/V2 (prefix, delimiter),
CopyObject, DeleteObject, DeleteObjects (batch), error handling.
Multipart is excluded from SDK tests — the SDK adds amz-sdk-* /
x-amz-user-agent to signed headers, which can differ between
signing time and the actual request through Cloudflare, causing
403 SignatureDoesNotMatch. Multipart is verified via manual
SigV4 signing in test/production-e2e.test.ts.
Fixes:
- RowList bug: postgres.js returns array directly, not {rows}. Fix in
buckets.ts, files-ext.ts, multipart.ts (3 files, 8 functions)
- S3 ListBuckets routing: GET / was intercepted by handleHome route
- Presigned URL detection: isS3Request() only checked Authorization header
- FK constraint on bucket delete: cascade-delete files & multipart rows first
- docker-compose.yml: pass S3_ACCESS_KEY / S3_SECRET_KEY to container
- Dockerfile: copy home.html to runner stage for handleHome
Tests:
- test/production-e2e.test.ts: 29 tests (11 Web API + 18 S3 SigV4)
All pass against https://upload.asepharyana.my.id
- Creates and cleans up real buckets/objects on production
- Updated formatting in web-api.ts for better alignment and readability.
- Enhanced XML builders in xml.ts for clearer structure and maintainability.
- Improved test cases in s3-auth.test.ts and s3-operations.test.ts for better clarity and consistency.
- Refactored mock data in web-api.test.ts for improved readability.