Ownware
Home›Gymora›Manual
Gymora · Manual

Gymora Manual, as shipped in the download

Gymora — User Manual

A gym membership register and front desk, on your own server. Version 3.1.4 [src: app/controllers/api.php:16].

About this manual

Every statement here was written by reading Gymora's own source, and each one carries the file it came from in a bracketed src marker. Paths are relative to the folder this docs directory sits in. If a sentence disagrees with the application, the application is right and this manual has a bug — the markers exist so you can check rather than take our word for it.

Nothing is described that the code does not do. Where something is deliberately limited, that is said plainly rather than left out.

The question the product exists to answer

A gym's front desk lives on one question asked hundreds of times a day: can this person walk in right now? The answer depends on membership status, an end date that may have quietly passed, and — for class packs — how many credits are left. Getting it wrong means either turning away a paying member or letting an expired one train for free [src: app/src/Membership.php:5].

All of that logic sits in pure functions with no database and no network, which is what makes it testable in isolation [src: app/src/Membership.php:11].

Money is integer cents, dates are YYYY-MM-DD, and "today" is computed in the business's configured timezone [src: app/src/Membership.php:16].

The check-in guard

The guard reads only three things — status, end date and credits remaining — and returns whether the person may come in, why not if they may not, and whether a credit should be burned [src: app/src/Membership.php:170].

The refusals are specific rather than generic [src: app/src/Membership.php:178]:

SituationWhat the desk is told
No membership"No active membership on file. Assign a plan before checking in."
Frozen"Membership is frozen. Unfreeze it before checking in."
Cancelled"Membership has been cancelled."
Expired"Membership expired on date. Renew to continue." — naming the date where one is known
Class pack, no credits"No class credits remaining. Renew the pass to continue."

A person on the desk can read any of those aloud to the member without translating it.

Status is computed, not just stored

A membership's status "as of today" is the stored status, except that an active membership whose end date has passed is really expired [src: app/src/Membership.php:157].

Frozen and cancelled are manual holds and are never flipped automatically [src: app/src/Membership.php:153]. No membership at all reports as none.

So a membership cannot sit in the database claiming to be active a month after it ran out.

Legal transitions

The transition table is explicit [src: app/src/Membership.php:219]:

FromMay move to
activefrozen, expired, cancelled
frozenactive, expired, cancelled
expiredactive (by renewal), cancelled
cancellednothing — terminal

A move to the status it already holds is refused [src: app/src/Membership.php:233].

Plans and terms

Four billing periods [src: app/src/Membership.php:27], and each produces a different term [src: app/src/Membership.php:244]:

  • Monthly — one month from the start date.
  • Annual — twelve months from the start date.
  • Class pack — no end date at all; it carries credits instead.
  • Day pass — starts and ends on the same day.

Credits

A check-in on a credit-based membership burns one [src: app/src/Membership.php:206], and the count never drops below zero [src: app/src/Membership.php:213]. A membership with no credit count is unlimited, and stays unlimited [src: app/src/Membership.php:213].

"Expiring soon"

Only an active, dated membership whose end date falls between today and today plus your window counts as expiring [src: app/src/Membership.php:275].

Already-expired, frozen, cancelled and open-ended memberships never appear in that list — a class pack has no end date to be near [src: app/src/Membership.php:277].

Users and roles

Three roles, chosen against how a gym actually staffs a front desk [src: app/controllers/app.php:14]:

  • viewer — reads the register and reports; a bookkeeper or an owner's accountant [src: app/controllers/app.php:15].
  • member — everything a front-desk shift needs: check people in, take a payment, book a class. Cannot change plans or prices, cannot touch settings, cannot delete [src: app/controllers/app.php:16].
  • admin — the owner: plans, pricing, settings, roles, integrations and destructive actions [src: app/controllers/app.php:18].

Existing users migrate to admin on upgrade, because an upgrade never removes access [src: app/controllers/app.php:19].

This table was produced by executing the product's own permission function against every permission the routes ask for.

Permissionviewermemberadmin
member.viewyesyesyes
class.viewyesyesyes
report.viewyesyesyes
audit.viewyesyesyes
checkin.create—yesyes
payment.record—yesyes
member.edit—yesyes
class.book—yesyes
class.edit——yes
member.delete——yes
settings.edit——yes
team.manage——yes

⚠️ The audit trail is readable by a viewer

Worth stating plainly, because it is easy to assume otherwise.

Gymora's role map grants the viewer role *.view — a wildcard rather than a list [src: app/controllers/app.php:24]. Because the audit permission is itself named audit.view, the wildcard matches it, so a viewer can open the audit trail and download it as CSV [src: app/controllers/v3.php:16], [src: app/controllers/v3.php:26].

The audit trail records who did what and when, filterable by actor, action, entity and date.

The source's own comment lists audit.view among the permissions the admin role "also covers … via the '*' wildcard" [src: app/controllers/app.php:26], which reads as though it were admin-only. The behaviour and that comment do not agree, and the behaviour is what this manual describes.

If your viewer seat is an outside bookkeeper, know that it currently includes the staff activity record. This has been raised against the product.

Backups follow the model

Gymora's database backup downloads are permission-gated, requiring the settings permission [src: app/controllers/api.php:257]. A viewer cannot download the database.

The API and agent access

The agent endpoint uses the same key as the REST API, so revoking a key revokes the agent [src: app/controllers/mcp.php:37]. A key carries a scope, and read and write are enforced per tool rather than per method [src: app/controllers/mcp.php:38].

Five tools, of which one writes [src: app/controllers/mcp.php:51]: listing members with their check-in eligibility, member detail, expiring memberships and a revenue report on the read side; check_in on the write side.

The instructions tell an agent the guard up front — that the server refuses a check-in with no credits left, or a frozen, cancelled or expired membership, or none at all — and that check_in writes and may spend a credit [src: app/controllers/mcp.php:46].

What Gymora does not do

It does not take payments. It records that one was taken; payment.record is a front-desk permission.

It does not decide your prices or plans. Those are the owner's, behind the settings permission [src: app/controllers/app.php:18].

It does not auto-unfreeze or auto-cancel. Frozen and cancelled are manual holds [src: app/src/Membership.php:153]; only the passage of an end date is applied automatically.

It does not let a cancelled membership come back [src: app/src/Membership.php:219]. Cancelled is terminal; a returning member gets a new membership.

← Back to Gymora · Quickstart · API · Test run

Affiliate program
Recommend tools people own — earn 35% on every sale. 90-day tracking, instant delivery, payouts by Lemon Squeezy.
Become an affiliate →