Skip to content

feat(core): file domain + tenants + fiely-storage-local plugin - #5

Open
benjaminLedel wants to merge 4 commits into
mainfrom
claude/plan-next-priorities-bA3GS
Open

feat(core): file domain + tenants + fiely-storage-local plugin#5
benjaminLedel wants to merge 4 commits into
mainfrom
claude/plan-next-priorities-bA3GS

Conversation

@benjaminLedel

Copy link
Copy Markdown
Owner

Adds the file tree the product revolves around, backed by a new pluggable
storage layer. Covers items #1 and #2 of the next-priorities plan.

Schema (V3, V4):

  • tenants table with max_upload_bytes setting; seeds a stable default tenant
  • auth_users.tenant_id with a DB default so the existing jwt-plugin
    UserRepository keeps working unmodified
  • files: single self-referential table, folder-XOR-blob CHECK, unique name
    per (owner, parent) + partial unique index for the root

Core:

  • FileEntity / FileRepository / FileService / FileController with OpenAPI
    annotations matching AuthController's style
  • CurrentUserResolver factors bearer-token -> UserInfo + tenant lookup out of
    the controllers (no Spring Security; matches existing ad-hoc pattern)
  • Tenant entity + repository; per-tenant upload cap enforced in FileService
  • REST surface: POST /api/files (multipart), POST /api/files/folder,
    GET /api/files, GET /api/files/{id}, GET /api/files/{id}/content (streamed),
    PATCH /api/files/{id} (rename/move), DELETE /api/files/{id} (recursive)

Plugin fiely-storage-local:

  • Implements StorageProvider with UUID-sharded filesystem layout
    (/////v)
  • Atomic-move semantics so partial writes never materialise
  • Path-escape defenses on all ref resolutions

Tests:

  • LocalStorageProviderTest: round-trip, prune-on-delete, path-escape,
    partial-write rejection
  • FileControllerTest: end-to-end via H2 + stubbed AuthProvider/StorageProvider;
    covers upload/list/download/delete, 401/409/413, folder nesting, rename,
    move-into-descendant rejection
  • FilePluginIntegrationTest: real Postgres + real plugin JARs + real JWT,
    round-trips bytes through the filesystem (skipped without Docker, same
    pattern as AuthPluginIntegrationTest)

Build:

  • kotlin("plugin.jpa") applied to fiely-core so Hibernate can instantiate
    entities
  • copyTestPlugins now bundles fiely-storage-local alongside fiely-auth-jwt

Configuration:

  • fiely.storage.provider (active provider id)
  • fiely.plugins.fiely-storage-local.root (blob root)
  • spring.servlet.multipart.max-file-size raised to 1GB as a transport
    ceiling; per-tenant limit is the real cap

claude added 4 commits April 15, 2026 17:07
Adds the file tree the product revolves around, backed by a new pluggable
storage layer. Covers items #1 and #2 of the next-priorities plan.

Schema (V3, V4):
- tenants table with max_upload_bytes setting; seeds a stable default tenant
- auth_users.tenant_id with a DB default so the existing jwt-plugin
  UserRepository keeps working unmodified
- files: single self-referential table, folder-XOR-blob CHECK, unique name
  per (owner, parent) + partial unique index for the root

Core:
- FileEntity / FileRepository / FileService / FileController with OpenAPI
  annotations matching AuthController's style
- CurrentUserResolver factors bearer-token -> UserInfo + tenant lookup out of
  the controllers (no Spring Security; matches existing ad-hoc pattern)
- Tenant entity + repository; per-tenant upload cap enforced in FileService
- REST surface: POST /api/files (multipart), POST /api/files/folder,
  GET /api/files, GET /api/files/{id}, GET /api/files/{id}/content (streamed),
  PATCH /api/files/{id} (rename/move), DELETE /api/files/{id} (recursive)

Plugin fiely-storage-local:
- Implements StorageProvider with UUID-sharded filesystem layout
  (<root>/<tenant>/<xx>/<yy>/<fileId>/v<N>)
- Atomic-move semantics so partial writes never materialise
- Path-escape defenses on all ref resolutions

Tests:
- LocalStorageProviderTest: round-trip, prune-on-delete, path-escape,
  partial-write rejection
- FileControllerTest: end-to-end via H2 + stubbed AuthProvider/StorageProvider;
  covers upload/list/download/delete, 401/409/413, folder nesting, rename,
  move-into-descendant rejection
- FilePluginIntegrationTest: real Postgres + real plugin JARs + real JWT,
  round-trips bytes through the filesystem (skipped without Docker, same
  pattern as AuthPluginIntegrationTest)

Build:
- kotlin("plugin.jpa") applied to fiely-core so Hibernate can instantiate
  entities
- copyTestPlugins now bundles fiely-storage-local alongside fiely-auth-jwt

Configuration:
- fiely.storage.provider (active provider id)
- fiely.plugins.fiely-storage-local.root (blob root)
- spring.servlet.multipart.max-file-size raised to 1GB as a transport
  ceiling; per-tenant limit is the real cap
Adds a structure for per-file metadata: namespaced JSON documents keyed by
(file_id, namespace). Each producer owns its namespace — `user` for
caller-supplied tags/ratings/notes, `exif`/`pdf` for extractors,
`fiely-ai-*` for model outputs — so writes don't collide.

Schema (V5):
- file_metadata(file_id, namespace, data TEXT, timestamps); PK(file_id,
  namespace). `data` is TEXT for H2-portability; migrating to JSONB when we
  need GIN is a one-line ALTER.

Core:
- FileMetadataEntity with @EmbeddedId (FileMetadataId)
- FileMetadataRepository with list/delete helpers
- FileMetadataService with auth-scoped CRUD; namespace validated against
  [A-Za-z0-9][A-Za-z0-9._-]* so plugin ids fit
- FileMetadataController under /api/files/{id}/metadata[/{namespace}]:
  GET list, GET one, PUT (upsert verbatim), DELETE

Refactors:
- FileService.delete no longer relies on DB ON DELETE CASCADE to prune
  subtrees. Hibernate's auto-generated DDL (used in H2-backed tests)
  doesn't carry our entities' FK cascades, so the service now walks the
  subtree, drops blobs via StorageProvider, nukes metadata rows, then
  deletes file rows children-first. Works identically in Postgres.
- File exception handlers moved out of FileController into a shared
  @ControllerAdvice scoped to cloud.fiely.file.web so both controllers
  map NotFoundException / ConflictException / etc. to HTTP statuses.
- Download tests now use asyncDispatch, the correct pattern for
  StreamingResponseBody in MockMvc.

Tests:
- FileMetadataControllerTest: PUT/GET/list/DELETE round-trip, multi-
  namespace coexistence, cascade-on-file-delete, cross-owner 404,
  invalid-namespace 400, missing-namespace 404, unauthenticated 401
Adds the post-login experience: React Router, JWT session management,
an app shell header, and a full file browser that exercises every file
API endpoint.

Routing:
- / → redirects to /files (authenticated) or /login (not)
- /login → login page, redirects to /files if already authenticated
- /files/:folderId? → file browser, scoped to a folder or root

Auth:
- AuthProvider context wraps the app; calls /api/auth/me on mount to
  verify the stored token and resolve the current user
- useAuth() hook exposes { user, loading, logout, refreshUser }
- apiFetch() wrapper auto-attaches the Bearer header and redirects to
  /login on 401
- Login.tsx updated to use navigate() + refreshUser() instead of a full
  page reload

App shell:
- Header with logo, user display name, sign-out button
- Outlet for routed content
- Responsive (name hidden on small screens, icons always visible)

File browser:
- Lists files/folders from GET /api/files (folders first, alphabetical)
- Breadcrumb trail — built on navigation, resolves on deep-link via
  walking parentId chain from the API
- Upload (multipart, multi-file) via hidden input + styled button
- Create folder via window.prompt
- Rename via PATCH + window.prompt
- Delete via DELETE + window.confirm (recursive for folders)
- Download via blob URL
- Empty state illustration
- File size formatting
- Hover-reveal action buttons (download, rename, delete)

Dependencies:
- react-router-dom ^7.14.2 (only new dep)
Replace window.prompt/confirm with proper Tailwind modals, add
drag-and-drop upload, and refine the app shell and file list to match
the login page's design quality.

App shell:
- Sticky header with backdrop blur
- User avatar initial circle (brand-colored)
- Subtle separator between user and sign-out

File browser:
- Table layout with Name / Size / Modified columns (responsive)
- Relative timestamps ("3h ago", "5d ago", or "Apr 25")
- Drag-and-drop upload zone with dashed border + icon overlay
- Refined empty state with icon container
- Hover-reveal row actions remain, now in table cells

Modals (new components):
- Modal — reusable backdrop + card, Escape to close, click-outside
- ConfirmDialog — wraps Modal with confirm/cancel buttons and
  destructive variant (red icon + button)
- Used for: create folder, rename, delete confirmation

No new dependencies (react-router-dom added in prior commit).
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.

2 participants