Points for chores, homework and good habits - a family app where kids earn points and spend them in a family shop. Русская версия
Merito (Latin for "deservedly") turns a family's rules into a simple loop: a child reports what they did, a parent confirms it and awards points, and the child spends the points on rewards the family agreed on - screen time, a trip, a treat. The user interface is in Russian.
For parents
- Create a family and fill it in minutes: tasks (daily and extra), penalties and a reward shop - or start from a ready example catalog with 21 tasks, 7 penalties and a screen-time shop.
- Add children as login-and-password accounts (no email needed) or invite anyone with a code - a second parent or a child who already has an account.
- Review what children reported: approve with the suggested points or your own, reject with a reason, and save a new kind of work into the task list in the same step.
- Grant or deduct points by hand - a comment is always required - or apply a penalty from the catalog.
- Hand over bought rewards or cancel a purchase with a refund.
- See every child's balance and the full history of points.
For children
- One tap from the home screen: "I did it" or "Shop".
- Pick a task from the list or describe something that is not in it.
- Buy rewards when the balance covers the price; see pending reviews and the own history.
Everywhere
- Mobile first: a bottom navigation bar on phones, a side menu on wide screens.
- Installable PWA with light and dark mode.
- Client: Blazor WebAssembly PWA on Flare with the Material 3 Expressive theme.
- Server: ASP.NET Core minimal APIs, ASP.NET Core Identity (bearer tokens), EF Core with PostgreSQL. The server also hosts the WebAssembly client, so the app and its API share one origin.
- Tests: xUnit v3 - domain services on SQLite in memory and the real HTTP pipeline through
WebApplicationFactory, including access checks for strangers and children.
Requirements: Docker with Compose v2.
cp .env.example .env # set POSTGRES_PASSWORD
docker network create proxy_network # once; skip if it already exists
docker compose up -d --buildThe stack has two containers: merito (the app, listening on port 8080 inside the container) and
merito-db (PostgreSQL 16 with the merito-pgdata volume). Database migrations run when the app starts.
merito joins the external proxy_network and publishes no ports: point your reverse proxy (for
example Nginx Proxy Manager on the same network) at http://merito:8080. To open it directly on this
machine instead, add a local docker-compose.override.yml (ignored by git):
services:
merito:
ports:
- "5080:8080"and open http://localhost:5080. On Windows, run-docker-compose.bat rebuilds and restarts the stack.
The first account you register is a parent account; create the family, then add children from the "Семья" (Family) page.
| Variable | Default | Purpose |
|---|---|---|
POSTGRES_PASSWORD |
- (required) | Database password used by both containers |
POSTGRES_DB |
merito |
Database name |
POSTGRES_USER |
merito |
Database user |
WebPush__PublicKey |
empty | Public VAPID key sent to browsers when they subscribe |
WebPush__PrivateKey |
empty | Private VAPID key used only by the server |
WebPush__Subject |
empty | Contact URI for VAPID claims (mailto: or https:) |
Web Push is optional. If its three variables are empty, Merito starts normally and keeps browser push
disabled. To enable it, generate one VAPID key pair and keep using that pair for this installation.
With Node.js and npx installed, run:
npx --yes web-push generate-vapid-keys --jsonIf you only have Docker, run the same generator in a temporary container:
docker run --rm node:lts-alpine npx --yes web-push generate-vapid-keys --jsonCopy the generated values to .env and set a contact URI owned by the server operator:
WebPush__PublicKey=<publicKey>
WebPush__PrivateKey=<privateKey>
WebPush__Subject=mailto:admin@example.comWebPush__Subject may be a mailto: address or an https: URL. The public key is intentionally sent
to browsers. The private key must remain secret: keep it only in .env or a production secret store,
and never commit it. Generate the pair only once; replacing it makes existing browser subscriptions
unusable, so users must subscribe again.
Serve the app over HTTPS in production (the reverse proxy is the natural place): sign-in tokens travel in request headers, and browsers install PWAs only from secure origins.
Every version tag (v1.2.3) publishes the image to Docker Hub as frigat/merito and to GitHub
Container Registry as ghcr.io/jrfrigat/merito, tagged X.Y.Z and latest. To run a release
instead of building from source, replace the build: section of the merito service with:
image: frigat/merito:latest # or a pinned X.Y.Z tagRequirements: .NET SDK 10.0.400 or newer.
# a throwaway database for local runs
docker run -d --name merito-dev-db -e POSTGRES_USER=merito -e POSTGRES_PASSWORD=merito \
-e POSTGRES_DB=merito -p 127.0.0.1:5433:5432 postgres:16-alpine
dotnet user-secrets set ConnectionStrings:Merito \
"Host=localhost;Port=5433;Database=merito;Username=merito;Password=merito" --project src/Merito.Server
dotnet run --project src/Merito.Server # http://localhost:5080
dotnet test --solution Merito.slnxSchema changes go through EF Core migrations:
dotnet ef migrations add <Name> --project src/Merito.Server --output-dir Data/Migrationssrc/
Merito.Shared/ API contracts, enums and input limits - no dependencies
Merito.Server/ API, EF Core, Identity, domain services (Features/*); hosts the client
Merito.Client/ Blazor WebAssembly PWA built from Flare components
tests/
Merito.Server.Tests/
scripts/
make-icons.ps1 renders the PWA icons
Rules the code keeps:
- The server is the only source of truth about points. A balance changes only together with an immutable ledger entry, guarded by a concurrency token, so a balance cannot be spent twice.
- Every family endpoint checks membership and role; a child sees only their own submissions, purchases and history.
- The UI is assembled from Flare components and their parameters, with no custom stylesheets.
- Weekly savings bonus and a "week without penalties" reward.
- Shop rules: a break between screen-time packages, rewards available only on certain days.
- An optional family setting that approves tasks from the list automatically.