feat: shared plan registry — publish a week, browse and import others' - #21
Open
bwndapp wants to merge 1 commit into
Open
feat: shared plan registry — publish a week, browse and import others'#21bwndapp wants to merge 1 commit into
bwndapp wants to merge 1 commit into
Conversation
openGym already has the hard part: plan-share.js defines a portable bundle (routines, week schedule, referenced customs — never workouts, weigh-ins or settings) and mergePlan() adds it non-destructively. Sharing it still means handing someone a file. This adds storage and discovery around that same format, so a plan can be published to the instance you already run and imported in a tap. Backend (api/server.js, no new dependencies): GET /api/plans browse, ?q= &sort=popular|new &mine=true GET /api/plan?slug= one plan, with its bundle POST /api/plan/publish author comes from the session, never the body POST /api/plan/imported popularity counter, best-effort POST /api/plan/delete author or admin One JSON file per plan under DATA/plans, written atomically via the existing helper. Publishing is validated with the same rules parsePlan applies client-side, so a plan the app would refuse to import can't land on disk. Slugs are constrained to [a-z0-9-] before they ever reach the filesystem. Rate limited per author (10/hour, 50 total). Browse metadata is derived from the bundle, never from what the client claims. It deliberately excludes anything needing the exercise catalogue — the api image ships server.js alone, and PlanDetail derives the equipment chips client-side where the catalogue already lives. The UI hides itself in the mobile and demo builds, which have no backend to talk to. Tests: 6 cases over the registry client, alongside the existing suite (198 passing).
|
Not landing in #28. A shared plan registry is a new public write surface on the API (publish/browse/import counters) and needs a dedicated security review (authz on delete, slug uniqueness, size limits, invite-only instances). Please keep this PR open for that review rather than bundling it with the secret/media hygiene fix. |
|
Heads up: openGym has moved to GitLab. The GitHub repo is no longer maintained (the account was suspended, so it's out of my hands). Please re-open this issue/PR on GitLab so it doesn't get lost, I'll pick it up there. Questions, help and roadmap discussion now happen in Discord: Sorry for the noise, and thanks for sticking around. |
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.
What
Publish your weekly plan to the openGym instance you already run, browse what others published, and import one in a tap.
openGym already has the hard part.
plan-share.jsdefines a portable bundle — routines, the week schedule and any custom exercises they reference, deliberately carrying no workouts, weigh-ins or settings — andmergePlan()adds it non-destructively. Sharing it still means handing someone a file. This adds storage and discovery around that same format, so it's not a second import path.Backend —
api/server.js, no new dependenciesOne JSON file per plan under
DATA/plans, written through the existingatomicWrite. At this scale a directory beats a table: a plan is readable without a query and a bad one is removable withrm. Route keys and query strings follow the existing table's style.Some things I was deliberate about:
parsePlanapplies client-side, so a plan the app would refuse to import can't land on disk.[a-z0-9-]before they ever reach the filesystem — a slug is a filename, so it isn't trusted just because we generated the ones already there.A note on the exercise catalogue
The natural way to label a plan is by equipment and body part, which needs
exercises-data.js. The api image copiesserver.jsalone, and pushing 800 kB of frontend dataset into the backend to render a few chips seemed like a bad trade against "keep it dependency-light".So the server derives only what the bundle itself knows (routines, days, exercise count, customs), and
PlanDetailderives the equipment chips client-side, where the catalogue already lives. The consequence is that server-side search matches name, description and author, not equipment — the search placeholder says so. Happy to move it either way if you'd rather.Frontend
Plans.jsx(browse, search, sort) andPlanDetail.jsx(full week, muscle map, import) as new views, a thinplans-api.jsclient, and entry points from the Plan tab, Settings and the existing share sheet. Everything is gated behindregistryOn(), which is false in the mobile and demo builds since they have no backend to talk to.Testing
npm test— 198 passing, including 6 new cases over the registry client (query building, slug encoding, error surfacing, and that the popularity counter can never fail an import that already happened).minewhile signed out, the counter on a real and a missing slug, path traversal via?slug=../../db(404, not a file read), delete by a non-owner (403) and by the owner (200).Open questions
Happy to adjust any of this — in particular whether the registry should sit behind an env flag (like
INVITE_ONLY) for instances whose operator doesn't want user-published content, and whetherPOST /api/plan/deleteshould beDELETEinstead. I matched the existing table, which uses POST for mutations like/api/admin/user/disable.