Onboard recipe-backend onto @recipe/contract #13
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "onboard-recipe-platform-contract"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements
docs/plans/onboarding-integration.md, including the base-mapper blocker fix requested after initial review.What's in this PR
@recipe/contract(file:../recipe-platform/api-contract) topackage.jsonand arecipe-platformsibling-clone step to all three Forgejo workflows, perrecipe-platform/docs/adr/2026-09-19-contract-ci-strategy.md.docs/audit-tags-and-domains.md.GET /tag/all/GET /tag/enrichednow return{ tags: [...] }wrapper objects instead of bare arrays, per the list-wrapper convention. This is a breaking change and needs to land together with therecipe-appPR that unwraps the new response shape — please hold merging this until that PR is ready, or coordinate the merge order.LoginResponse.expiryDate/ refresh'sexpiryDatenow serialize as ISO-8601 strings instead ofDate, matching the contract.AbstractDtoEntityMapper(extended by every concrete mapper) now serialises dates as ISO strings at the entity↔DTO boundary instead of rawDateobjects, matching the contract'sAbstractDto. This was the blocker preventing any domain beyondhealthfrom migrating; seedocs/audit-tags-and-domains.mdfor the full writeup, including the follow-on optionality guards each write-direction mapper needed once the corresponding DTOs wentoptionalper the contract's shared read/write shape.commentsandimagesnow imports its DTOs directly from@recipe/contract, with all local DTO files deleted:health,ratings,compact-recipes,users(incl.UserDto/UserRole),ai(incl.RecipeImportStatus/RecipeImportReason),meal-plans(incl.MealPlanPermission/MealPlanEffectivePermission),passkeys(incl.PasskeyDeviceType),auth,recipes, andtags.check-platformskill added, mirroringrecipe-platform'scheck-siblingsskill in reverse.CLAUDE.mdupdated with a Multi-Repo Context section reflecting the completed migration and a pointer torecipe-platform/docs/api-conventions.mdinstead of restated convention prose.Explicitly NOT done here
commentsdomain left unmigrated — the audit found a real behavioural gap: the contract'screateOrUpdateJSDoc says admins may edit any comment, butCommentHandler.updateCommenthas no admin bypass (unlikedeleteComment, which does checkUserRole.ADMIN). This needs a human product decision (fix the handler, or correct the contract), not a wire-format call — flagged indocs/audit-tags-and-domains.md, not fixed.imagesdomain — no local DTO exists under this repo for theGET /image/:keyroute; separately, the contract's path pattern for it is inaccurate (real storage keys are multi-segment) — that's arecipe-platform-side documentation fix, not something this PR can do.zoddependency inapi-contract/package.json(declared, unused) — that's arecipe-platform-side fix.npm run buildandnpm test(364 tests) both pass.🤖 Generated with Claude Code
GET /tag/all and GET /tag/enriched returned bare arrays; the contract requires TagListResponse/EnrichedTagListResponse wrapper objects per docs/api-conventions.md's list-wrapper convention. This is a breaking wire-format change and must land together with the recipe-app PR that updates the two tag-fetching calls to unwrap { tags: [...] }.users/shared: - Deletes local UserDto, CreateUserRequest, CreateUserResponse, ChangeUserPasswordRequest, UserListResponse — all imported from @recipe/contract now. - Deletes the local UserRole TS enum; UserEntity's @Column({ type: "enum", enum: UserRole }) now references the contract's as const object directly (TypeORM only reads Object.values() off it, so this is a drop-in swap). UserRoleHelper (display names, dropdown options — no contract equivalent) stays local in src/api/enums/UserRole.ts, now built on the contract's type. - UserDtoEntityMapper.doMergeDtoIntoEntity explicitly validates userName/email presence, since both are optional on the contract's UserDto (same shape covers partial reads) but required to persist a user. - Updates every other file that referenced the local UserRole/UserDto (AuthPayload, CommentDto, MealPlanShareDto, handlers, middleware, services) to import from @recipe/contract instead. auth: - Fully migrates LoginRequest/LoginResponse/RefreshResponse to @recipe/contract (LoginResponse was previously just fixed in place; now imported directly since it has no remaining local-type coupling). passkeys: - Deletes local PasskeyDto, PasskeyListResponse, PasskeyAuthBeginResponse, PasskeyRegisterBeginResponse, PasskeyAuthCompleteRequest, PasskeyRegisterCompleteRequest, PasskeyRenameRequest, and the PasskeyDeviceType TS enum (zero TypeORM coupling — UserPasskeyEntity.deviceType is a plain string column). - PasskeyApplicationService now casts the contract's `unknown`-typed WebAuthn response/options fields to the concrete @simplewebauthn/server types PasskeyHandler expects, per the contract's own documented rationale for typing them loosely. Converts every `new XDto()`/`new XRequest()` construction site touched by this change (production and test) to object literals, since none of these types can be `new`'d once imported as contract interfaces.Deletes MealPlanDto, MealPlanDayDto, MealPlanDayDishDto, MealPlanShareDto, MealPlanFilterRequest, MealPlanListResponse, CompactMealPlanDto, and the MealPlanPermission/ MealPlanEffectivePermission TS enums — all imported from @recipe/contract now. MealPlanShareEntity's @Column({ type: "enum", enum: MealPlanPermission }) now references the contract's as const object directly (same drop-in swap pattern as UserRole). Every write-direction mapper (MealPlanDtoEntityMapper, MealPlanDayDtoEntityMapper, MealPlanDayDishDtoEntityMapper, MealPlanShareDtoEntityMapper) explicitly validates the fields that are optional on the contract's DTOs (same shape covers detail reads) but required to actually persist a plan/day/dish/share: title, date, recipeId/servings/sortOrder, and sharedWithUserId/permission respectively — each throws ValidationError if missing, consistent with the pattern established in the tags/ratings/users migrations.@ -5,0 +12,4 @@| `recipe-backend` (this repo) | REST API server || `recipe-app` | React SPA frontend |`@recipe/contract` (installed via `file:../recipe-platform/api-contract`) is the sourcethis is lots of historic notes instead of the current state. is that necessary? It seems like it's filling the context unnecessarily.
@ -5,0 +26,4 @@`docs/audit-tags-and-domains.md` for the full domain-by-domain audit, including the`AbstractDtoEntityMapper` date-typing fix every migrated mapper depends on.Cross-cutting API conventions (query/filter endpoints use POST, named-verb mutations,This is quite a lot of text for a simple "There's a strict rule on API conventions we MUST follow in ...
@ -5,0 +32,4 @@migrated mapper depends on.Cross-cutting API conventions (query/filter endpoints use POST, named-verb mutations,list-response wrappers, UUID/ISO-date wire formats, the `{ error }` envelope, optimisticAgain: What is that doing here? Why does this repo need to know about the parent repo's agents? Naively, I'd say it only needs to know how to check that the platform is there.
Here you refer to the same conventions document as above. Once should suffice.
@ -12,3 +2,3 @@/*** Helper functions for UserRole* Helper functions for UserRole. Backend-only display/validation concernsDoes this file still carry the correct name? We somehow reduced it to an API util - the UserRoleHelper.
@ -24,7 +23,8 @@ export interface LoginResult {*/export interface RefreshResult {The interfaces here are used by the frontend, right? Why are they local to the backend and not part of the contract?
@ -58,0 +59,4 @@// recipeId/servings/sortOrder are optional on the contract's// MealPlanDayDishDto (the same shape covers detail reads), but all are// always required to persist a dish.if (!dto.recipeId) throw new ValidationError("recipeId is required");We do that twice here. Would a validation method be better?
@ -18,3 +22,3 @@this.mapBaseDtoToEntity(dto, entity);if (!dto.text) throw new ValidationError("text is required");Again, validation in two places. Validation method?
@ -0,0 +269,4 @@1. ~~`comments` admin-edit permission~~ — **resolved**: fixed to match the contract and thefrontend's existing assumption (admins can edit any comment, same as delete). See above.2. **`api-contract/src/images.ts`'s `GET /image/:key` path pattern** doesn't reflect theAre 2 and 3 still open for follow-up?
Re: "Are 2 and 3 still open for follow-up?" (on docs/plans/onboarding-integration.md) — yes, both still open. Tracked as issues in recipe-platform now, so they do not get lost: