Centralize request audit recording, preserve immutable download-task history, and derive hourly statistics and backfills from the same authoritative sources. Add durable user registration facts so admin deletion no longer destroys signup history.
Retain purged matter and image metadata while hiding tombstones from live reads. Record exact storage deltas and build hourly trends only after the opening balance.
* feat: make forcePathStyle configurable per storage
Previously hardcoded to true, which breaks S3-compatible backends that require
virtual-hosted-style addressing (e.g. Alibaba Cloud OSS). Now configurable via
admin storage settings with a toggle switch, defaulting to true for backwards
compatibility.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test: cover storage force path style
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: saltbo <saltbo@foxmail.com>
* feat(avatars): host avatars + team logos on Cloud via SDK 2.4.0; remove public-bucket mode
Host user avatars and org logos on the ZPan Cloud avatar service
(zpan-cloud-sdk ^2.4.0) instead of a public S3/R2 bucket, then remove the
now-dead storages.mode / public-bucket concept entirely (#456 parts 2-3).
- image-upload gateway: upload/delete via SDK uploadAvatar/deleteAvatar against
a bound Cloud client; validate mime (AVATAR_CONTENT_TYPES) + size
(MAX_AVATAR_BYTES) before the call; map cloud error codes to 400/403/413/500;
unbound instance returns 503 cloud_required (delete is a best-effort no-op).
- licensing-cloud: createAvatarUploadClient builds the client with a plain-object
bearer header so both the image content-type and Authorization survive hono's
per-request header merge (a Headers instance would be dropped).
- drop storages.mode (migration via drizzle-kit), StorageRepo.select() no longer
takes a mode, remove StorageMode / Storage.mode / mode schema+audit+UI+i18n and
the PUBLIC_IMAGES bucket + PUBLIC_IMAGES_URL wiring.
Agent-Profile: https://agent-kanban.dev/agents/f759c704c282d88a
* ci(deploy): drop dead PUBLIC_IMAGES R2 provisioning from CF deploy
The Cloud avatar migration removed the PUBLIC_IMAGES binding from
wrangler.toml, so the deploy workflow's R2 public-images steps are dead and
must go too — otherwise every CF deploy keeps re-provisioning a public-read
zpan-public-images bucket (the footgun #456 eliminates) and sets an unused
PUBLIC_IMAGES_URL secret. Removes the bucket-create, managed-public-URL, and
secret steps (steps.r2 was only consumed by the secret step). Also drops a
stale storage-modes line from the v2.0 roadmap.
Agent-Profile: https://agent-kanban.dev/agents/f759c704c282d88a
---------
Co-authored-by: Alex Chen <alex-chen@mails.agent-kanban.dev>
Resolve#448 — one upload entry point and an AIP-164 soft delete.
Upload: POST /objects now returns size-decided upload instructions
{ sessionId, partSize, urls }; the server picks single PutObject (<=5 GiB)
vs 5 GiB-part multipart (>5 GiB) and rejects >5 TiB. The client PUTs each
slice, reads its ETag, then POSTs them to
POST /objects/{id}/uploads/{sid}/completions (returns the live object).
DELETE /objects/{id}/uploads/{sid} aborts and discards the draft.
Trash: matters.status drops 'trashed' (enum is {draft,active}); trash is
tracked by the existing trashedAt timestamp. DELETE /objects/{id} now
soft-deletes; the recycle bin lives under /trash/objects (list roots, get,
restorations, purge). Empty-trash is a frontend loop over roots.
BREAKING CHANGE:
- removes PUT /objects/{id}/status and POST /objects/{id}/uploads
- PUT .../uploads/{sid}/status -> POST .../uploads/{sid}/completions {parts}
- DELETE /objects/{id} flips hard-purge -> soft-delete; permanent purge
moves to DELETE /trash/objects/{id}
- DELETE /trash removed; restore is POST /trash/objects/{id}/restorations
- matters.status enum loses 'trashed' (migration backfills to trashedAt)
The migration swaps the matters_active_name_uniq partial index to exclude
trashed rows (WHERE status='active' AND trashed_at IS NULL). The single-PUT
presign is header-free so the uniform slice uploader's raw PUT matches the
S3 signature. Go downloader client + agent reworked to the unified flow.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the unique constraint on cloud_order_id in quota_delivery_events so
that a same-order reversal event (decrease) can be delivered after the original
purchase event (increase). A non-unique index replaces the dropped constraint
to preserve query performance.
The resumeDeliveryEvent lookup is tightened to match only by eventId,
cloudRedemptionId, or code — never by cloudOrderId — so different events
for the same order are processed independently.
Add integration tests covering:
- same cloudOrderId increase → decrease both succeed and net to zero
- replaying the same decrease event is idempotent (no double-deduct)
- audit records decrease with action quota_storage_decrease and full metadata
- traffic decrease from same cloudOrderId processes independently
Update the in-memory test schema to match the new index definition.
Closes #v78t1unzz94w
Agent-Profile: https://agent-kanban.dev/agents/f68cfbce6456edb5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add license-state.ts helper for reading/writing license state as
system_options key-value pairs instead of a dedicated singleton table
- Rewrite refresh.ts, has-feature.ts, entitlement.ts, licensing-admin.ts,
licensing-refresh-runner.ts to use license-state helpers
- Generate migration 0014 to drop license_binding table
- Update all 10 test files to use setLicenseOptions instead of
db.insert(licenseBinding)
- All 2809 tests pass
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add `licenseBinding` singleton table (id=1) to server/db/schema.ts
- Correct `0012_image-hosting` journal idx from 11→12 (matches its prefix;
was left wrong by previous rename-only fix in ef1fab8), then auto-generate
migration 0013_licensing.sql via drizzle-kit
- New 0013_snapshot.json created; 0012_snapshot.json unchanged
- Add ProFeatures enum to shared/constants.ts
- Add shared/types/licensing.ts with LicenseEntitlement, ProFeature, BindingState
- Export new types from shared/types/index.ts
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
The manually-authored journal entries for 0010_shares and 0011_notifications
used when=1745xxxxxxxxx (April 2025), one year before the last applied
migration 0009 (when=1776200000000, March 2026). drizzle-kit migrator
orders by when, so dev databases already at 0009 silently skipped the new
migrations. CI passed because it starts from an empty DB.
- Fix the two timestamps to 1776300000000 / 1776400000000 so drizzle
sees them as new.
- Add CLAUDE.md rules: never hand-author migrations; always add tests
for src/lib/api.ts wrappers in the same PR.
Duplicate folder/file names silently created duplicates under the same
parent. Add DB-level partial unique index on (org_id, parent, LOWER(name))
for active rows, plus a centralized plan/commit helper threaded through
create, rename, move, copy, upload-confirm, and restore. 409 responses
open a Keep Both / Replace / Cancel dialog with sticky "apply to all"
for batch operations; case-insensitive match matches OS conventions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>