Repository navigation
Conversation
The product lifecycle now runs through one operations module (src/lib/operations/products.ts) that both the server actions and /api/v1 routes call, the way memberships do since #642. GET /products (new) and GET /products/{account_id} return {items, next_cursor} pages over the existing base64 LastEvaluatedKey cursor and filter what the caller may see; the /products page uses the same listing. POST /products/{account_id} replaces the 501, PATCH replaces the commented-out PUT, and DELETE actually deletes, with preserve_data. Creating over an existing product is now a 409 rather than a silent overwrite. The [repository_id] route segment is now [product_id]; URLs are unchanged. Closes #590. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saving the edit form with an empty description sent `description: undefined` through Zod and the spread to DynamoDB, which rejects the update; only the fields actually given now change. Delete minted storage credentials from the cookie cache, which is bound to the browser's session rather than the bearer token a request carries; it now always mints for the session's own identity, and a caller with none (a service account) gets a 403 instead of a 500. A cursor naming other attributes or another partition is now a 400 rather than a DynamoDB 500. An update with an empty product ID is not found without a lookup, the create schema no longer offers search_text, and the docs say a search page can hold more than limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
Claude finished @alukach's task in 15s —— View job ✅ No blocking issues — safe to merge. I read
Description is stale (advisory)
Simplify (ponytail)
Docs
💰 Estimated review cost: $0.16 · 0m14s · 6 turns |
The build's lint step rejects an empty block (no-empty). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Revalidation of the product's and account's pages moves from the server actions into the operations, so creates, edits and deletes over the API drop cached pages too. PATCH and DELETE answer a deactivated product the caller can't see with 404, as GET does, rather than a 403 that tells it exists. PATCH refuses an empty title, and answers with the product as GET does (account included). Delete tolerates a product with no metadata. Create's three lookups run at once. A stale cursor on /products starts over at page one instead of a 404. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alukach
added a commit
to source-cooperative/source-coop-cli
that referenced
this pull request
Oct 9, 2026
Adds `source-coop product`, the first gh-style command group from #21, over the `/api/v1/products` endpoints from source-cooperative/source.coop#651. A small API client sends the request and turns the API's `{"error": {code, message, field_errors}}` body into a message; the CLI does no validation of its own. Requests carry the login session's access token (or `SOURCE_TOKEN`, if set), and run signed out without one, which is enough to read public products. `create` and `edit` take fields from flags, from a JSON file (`--from-file`), or both. When stdin and stderr are terminals, whatever is still missing is prompted for with defaults: a title from the product ID, the data connections the account can use, and the visibilities the chosen one allows. `edit` with no flags prompts from the product's current values and sends only what changed. `delete` asks whether to keep the data and for the name to be typed back; without a terminal it needs `--yes`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017poyHYEyxWEJct9X8Q71qF
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #590. Part of #588, on the operations layer from #642.
What
The product lifecycle runs through one module,
src/lib/operations/products.ts, which the server actions and the/api/v1routes both call and only adapt — the same split memberships got in #642.GET /products?q=&tags=&featured=&cursor=&limit=/productspage reads the same listingGET /products/{account_id}?cursor=&limit={products}, cut off at 50{items, next_cursor}, pagedPOST /products/{account_id}GET /products/{account_id}/{product_id}PATCH /products/{account_id}/{product_id}PUT)DELETE /products/{account_id}/{product_id}?preserve_data=preserve_datapolicyLists return
{ "items": [...], "next_cursor": ... }over the existing base64LastEvaluatedKeycursor;limitis 1–100, default 20. All six are documented at/api/docsfrom the schemas the operations parse, and off the route-coverage allowlist. The route segment[repository_id]is now[product_id]; no URL changes, and thefeatured,membersandpermissionssub-routes moved with it.Behavior that changed
/productspage used to show every product in the public index, deactivated ones included; it now filters withListRepository, so a deactivated public product disappears for everyone but admins. An account's listing usescanListOnProfile, the rule the profile pages use, so owners and maintainers also see their deactivated products there.productsTable.createis an unconditional put, and the ID check in the form (useIdValidation) calls a product it can't see "available", so creating could silently overwrite someone's product. Creating under an account that doesn't exist is a 404.GETof a product the caller may not see: 401 with no credentials as before, but 403 (was 401) when signed in. A deactivated product the caller can't see is still a 404, and nowPATCHandDELETEanswer it with 404 too rather than a 403 that tells it exists.{ "error": { "code", "message", "field_errors"? } }, as everywhere since feat(api): operations layer, generated OpenAPI docs, and the memberships API #642./productspage a stale cursor starts over at page one, as it used to.PATCHrefuses an empty title (the edit form, which leaves an empty field as it was, can't send one) and answers with the product asGETdoes, account included.updateProductnow reach the error boundary instead of "Failed to update product. Please try again.", matching feat(api): operations layer, generated OpenAPI docs, and the memberships API #642;deleteProductstill surfaces the underlying reason.OrganizationalAccount.identity_idandServiceAccount.identity_idare documented as never present, sinceProduct.accountbringsAccountinto the OpenAPI document and the generator can't expressz.undefined()on its own.Deploy order
The consumers of these endpoints across the org were audited; one breaks. The data proxy lists an account's products for
s3://{account}/by reading.products, so data.source.coop#253 has to deploy first. It reads either shape and followsnext_cursor, so it is safe to ship ahead of this. The proxy treats 401 and 403 alike, so theGETchange doesn't affect it. Elsewhere,metadata-catalog-pipeline'sscripts/lib/seed.pyguards against getting a list back with"products" in body; it only matters when a product segment is empty, and checking"product_id" not in bodyalone fixes it. docs.source.coop's existence check, ops-source's crawler and the CLI read nothing that changes.Not done here
Delete mints storage credentials through Ory and STS on every call rather than reusing the cookie cache, which belongs to the browser and not necessarily to a bearer token's owner. That costs a UI delete a few seconds; passing the action's cached credentials into the operation would win them back.
getFeaturedProducts(homepage) still shows featured public products without theListRepositoryfilter, so a deactivated featured product can show there.With
qortags,listPublicreads ahead 10× and DynamoDB returns every match it read, so a search page can hold more thanlimit; the docs say so. Honoringlimitexactly means building the cursor from the last returned item.Actions.DisableRepositoryhas no callers now that the oldDELETEis gone.Unchanged from before but now in the published schema:
Product.accountis the whole account record, includingemailsandmetadata_private, on every anonymous read. ops-source's crawler readsaccount.emails, so trimming it is its own change.Docs
public/llms.txt: the account-listing line now describes the page shape and cursor, and a line for searching all public products is added.using-source/; creating, editing and deleting a product work the same way in the UI, so nothing there is dated.Testing
At
5a2f691c:npm run type-checkclean;npm run lintpasses;npx jest --forceExitpasses 88 suites, 938 tests.src/lib/operations/products.test.tscarries the rules the old action tests checked (authorization before lookups, connection checks, visibility,preserve_data, deletion through the proxy) plus listing, cursors, the 409, and blank fields;src/lib/actions/products.test.tstests the adapters; the two route suites keep the OIDC org-account cases against the real authorization rules, now asserting the page shape and 403. The route-coverage test validates the generated document.🤖 Generated with Claude Code