The GitHub Actions job "Required Checks" on texera.git/main has succeeded.
Run started by GitHub user github-merge-queue[bot] (triggered by 
github-merge-queue[bot]).

Head commit for run:
1996448b1c5f4e0906cc077f927a90648e28a1af / Tanishq Gandhi 
<[email protected]>
feat(file-service): complete the model backend for sharing and the hub (#7937)

### What changes were proposed in this PR?

#7922 gave models the file API the management UI needs. This PR adds the
last backend pieces, so model sharing and the hub can be built as
frontend-only work.

**Cover images** — `POST /{mid}/update/cover`, `GET /{mid}/cover` (307
redirect), `GET /{mid}/cover-url` (JSON, since `<img src>` cannot attach
the Authorization header on a private model). The `cover_image` column
already existed.

The resource-agnostic halves move into a shared `CoverImageUtils` and
`DatasetResource` is refactored onto it, net **−55 lines** there. The
image extension allowlist is a security control, not a convention — a
cover is handed to the browser as a presigned URL, so an active document
(`.svg`, `.html`) would be a stored-XSS vector, and a duplicated
allowlist drifts.

A cover path is validated to be `<version>/<file>` and opened
defensively. `FileResolver` needs five path segments and throws an
`IOException` that file-service registers no mapper for, so a bare file
name or a deleted version would otherwise surface as an opaque 500 — and
on the read path, one bad write would 500 every render of every card
showing that resource. The write path answers 400; the reads treat an
unresolvable cover as absent (`{"url": null}` / 404) so the card falls
back to its default.

**Anonymous version browsing** — `/{mid}/publicVersion/list` and
`/{mid}/publicVersion/{mvid}/rootFileNodes`. Without these a logged-out
visitor on a public model page gets metadata and then an error.

**Framework/format editing** — `POST /update/framework`, `POST
/update/format`, plus `other` in both whitelists. The labels were
create-only, so a mislabelled model had to be recreated. Both endpoints
share `normalizeLabel` with `createModel`, so the edit path cannot
reject a value the create path accepted: blank resets the framework to
the default and clears the format, and values are trimmed the same way
in both.

**Routing** — `/api/model` and `/api/access/model` in nginx, the k8s
gateway and the frontend dev proxy. Nothing reaches the resource in a
real deployment without them. The nginx location carries a trailing
slash for symmetry with `/api/access/dataset/`; the LLM `/api/models`
route is an exact-match location, which outranks any prefix location, so
the two are unambiguous either way.

One behavior change on the dataset side: covers now get a path-length
check. `dataset.cover_image` is `varchar(246)` via `sql/updates/18.sql`,
so an over-long path was a jOOQ-wrapped 500 and is now a 400. The
dataset cover endpoints also pick up the unresolvable-path handling
above, which they lacked before this branch.

### Any related issues, documentation, discussions?

- Part of #6501
- **Reviewable on its own.** #7922 has merged and `main` has been merged
in, so the diff is this PR's work alone. No migration, no schema change.
- Independent of #7930 (models in the hub and unified search). That PR
is amber-side — the dashboard search builders,
`EntityTables`/`EntityType`, and `sql/42.sql`; the `ModelResource` it
touches is the amber dashboard one, not file-service's. Neither PR calls
the other, and this branch compiles and passes its tests on `main` with
none of #7930 present. They can merge in either order; whichever lands
second will hit a one-line conflict in `ModelResourcePermissionsSpec`'s
`publicEndpointMethods` set.

### How was this PR tested?

New `CoverImageUtilsSpec` pins the allowlist, `.svg`/`.html` rejection,
path traversal, absolute paths, the `<version>/<file>` shape and both
column-width limits. New `ModelHubApiSpec` drives the covers end to end
through LakeFS/MinIO and covers anonymous browsing, including the
negative cases: private model anonymous and no-grant, unknown model and
version id, a model unpublished after the fact, a bare filename and a
nonexistent version rejected at write, a stored cover that stops
resolving, and agreement between the public and authenticated file-tree
endpoints. `ModelApiForUiSpec` covers the framework/format updates,
including trimming, blank-resets-framework and blank-clears-format.

`ModelResourcePermissionsSpec` needed the four new `@PermitAll` endpoint
names — the guardrail caught them, as intended. The dataset suites pass
unmodified, which is what confirms the shared `CoverImageUtils` did not
shift dataset behavior.

300 tests across the eight affected suites:

sbt "FileService/testOnly *CoverImageUtilsSpec *ModelHubApiSpec
*ModelApiForUiSpec *ModelResourcePermissionsSpec *ModelResourceSpec
*DatasetResourceSpec *DatasetResourcePermissionsSpec
*DatasetAccessResourceSpec"
    sbt scalafmtCheckAll "scalafixAll --check"

### Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Claude Opus 5)

---------

Co-authored-by: ali <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>

Report URL: https://github.com/apache/texera/actions/runs/33112880451

With regards,
GitHub Actions via GitBox

Reply via email to