Skip to content

Repository files navigation

LittleHelpers

LittleHelpers is a family chore management app. Parents create chores, assign them to children, and award points based on difficulty. Children mark chores as completed through the frontend. Each child can be given a monthly allowance goal – once enough points are earned, the app calculates how much of the allowance has been earned that month.

Key features:

  • Parent role: manage users, create/edit chores, view all children and their progress
  • Child role: view and complete assigned chores
  • Monthly allowance tracking with point goals and payout calculation
  • Per-child score history with a monthly chart
  • Parent bonus/deduction chores that affect a child's points directly
  • Full audit log of every completed chore including who performed it

Tech stack

Layer Technology
Orchestration .NET Aspire
Backend ASP.NET Core (.NET 10) – Controllers, HATEOAS
Frontend Angular (standalone components, signals, Bootstrap 5 dark mode)
Database PostgreSQL 17
Auth JWT (Bearer tokens)
ORM Entity Framework Core + Npgsql
Passwords BCrypt
Observability OpenTelemetry (traces, logs, metrics via Aspire Dashboard)

Project layout

LittleHelpers.sln
├── LittleHelpers.AppHost/        # Aspire orchestrator – defines all resources
├── LittleHelpers.ApiService/     # ASP.NET Core API
│   ├── Controllers/
│   ├── Data/                     # DbContext + EF migrations
│   ├── Models/                   # Entities and DTOs
│   └── Services/                 # Business logic, LinkWriter<T> (HATEOAS)
├── LittleHelpers.ServiceDefaults/# Shared Aspire service defaults (telemetry, health)
├── LittleHelpers.Tests/          # Unit and integration tests
└── LittleHelpers.Web/
    # Angular app (src/app/{core,features,shared})

Running with Aspire (development)

Prerequisites

First-time setup

The committed LittleHelpers.ApiService/appsettings.json now lists every supported API setting and keeps sensitive values blank. For local development, create LittleHelpers.ApiService/appsettings.Development.json (gitignored) and override the sensitive values you need:

{
  "ConnectionStrings": {
    "littlehelpers": "Host=localhost;Port=5432;Username=littlehelpers;Password=CHANGE_ME;Database=littlehelpers"
  },
  "Jwt": {
    "Key": "your-secret-key-min-32-characters-long",
    "Issuer": "littlehelpers-api",
    "Audience": "littlehelpers-client",
    "AccessTokenLifetimeHours": 168,
    "RenewTokenLifetimeHours": 336
  },
  "SeedAdminPassword": "YourAdminPassword123!",
  "MonthlyCycle": {
    "BreakpointDay": 27
  }
}

Tip: Generate a strong key with openssl rand -base64 48

Supported API appsettings keys

These are the properties currently read by the API from appsettings*.json / environment variables:

Property Default in committed appsettings.json Notes
Logging:LogLevel:Default Information Baseline application log level
Logging:LogLevel:Microsoft.AspNetCore Warning Reduces ASP.NET Core framework log noise
AllowedHosts * Standard ASP.NET Core host filtering
ConnectionStrings:littlehelpers (empty) PostgreSQL connection string; usually injected by Aspire or set explicitly outside Aspire
Jwt:Key (empty) Required secret, minimum 32 characters
Jwt:Issuer littlehelpers-api Used both when issuing and validating JWTs
Jwt:Audience littlehelpers-client Used both when issuing and validating JWTs
Jwt:AccessTokenLifetimeHours 168 Access token lifetime in hours
Jwt:RenewTokenLifetimeHours 336 Renewed token lifetime in hours
SeedAdminPassword (empty) Required on first startup when the database has no users
MonthlyCycle:BreakpointDay 27 Day of month when a new allowance cycle starts

Jwt:AccessTokenLifetimeHours and Jwt:RenewTokenLifetimeHours also fall back to 168 and 336 in code if omitted. MonthlyCycle:BreakpointDay validates 1-31; if the setting is omitted entirely, the code fallback is 1.

Start the application

aspire run

Aspire will start PostgreSQL in Docker, run the API, and serve the Angular frontend. The Aspire Dashboard (traces, logs, metrics) opens automatically.

If a previous instance is already running, the CLI will prompt to stop it first.

Useful Aspire commands

# Start the application
aspire run

# List resources and their status
aspire resource list

# View logs for a specific resource
aspire logs apiservice

# Stop the application
aspire stop

Changes to LittleHelpers.AppHost/AppHost.cs require restarting the application. Changes to the API or frontend hot-reload automatically.


Docker

The project ships two Dockerfiles, both built from the repository root (they need access to multiple projects).

API image (Dockerfile.api)

Multi-stage build: sdk:10.0 for compilation → aspnet:10.0 runtime (~95 MB). Runs as a non-root user.

# Build
docker build -f Dockerfile.api -t littlehelpers-api:latest .

# Tag for a registry
docker tag littlehelpers-api:latest ghcr.io/yourorg/littlehelpers-api:1.0.0

# Push
docker push ghcr.io/yourorg/littlehelpers-api:1.0.0

# Run standalone (requires a running PostgreSQL)
docker run -d \
  -p 80:80 \
  -e Jwt__Key="your-secret-key-min-32-characters" \
  -e Jwt__AccessTokenLifetimeHours="168" \
  -e Jwt__RenewTokenLifetimeHours="336" \
  -e SeedAdminPassword="YourAdminPassword123!" \
  -e MonthlyCycle__BreakpointDay="1" \
  -e ConnectionStrings__littlehelpers="Host=localhost;Port=5432;Username=littlehelpers;Password=secret;Database=littlehelpers" \
  littlehelpers-api:latest

Frontend image (Dockerfile.web)

Multi-stage build: node:22-alpine for ng buildnginx:1.27-alpine serving static files (~21 MB). The nginx config is a template – API_URL is substituted at container startup via envsubst, so the proxy target can be changed without rebuilding. The same startup templating also supports runtime override of web app metadata (name, short_name, description, lang, and HTML <title>).

# Build
docker build -f Dockerfile.web -t littlehelpers-web:latest .

# Tag for a registry
docker tag littlehelpers-web:latest ghcr.io/yourorg/littlehelpers-web:1.0.0

# Push
docker push ghcr.io/yourorg/littlehelpers-web:1.0.0

# Run standalone (proxies /api/* to the API)
docker run -d \
  -p 80:80 \
  -e API_URL="http://apiservice" \
  -e WEBAPP_NAME="My Family App" \
  -e WEBAPP_SHORT_NAME="FamilyApp" \
  -e WEBAPP_DESCRIPTION="Household chores and rewards" \
  -e WEBAPP_LANG="en" \
  -e WEBAPP_TITLE="My Family App" \
  littlehelpers-web:latest

The API_URL environment variable must point to the API service. When running both containers together, use the service name from docker-compose (http://apiservice).


Docker Compose

The docs/compose/ directory contains a ready-to-use Compose file and an example environment file.

docs/compose/
├── docker-compose.yml   # Full stack: postgres, apiservice, webfrontend
└── .env.example         # Template – copy to .env and fill in values

First-time setup

cd docs/compose
cp .env.example .env

Open .env and fill in all CHANGE_ME values:

Variable Description
POSTGRES_PASSWORD PostgreSQL password
JWT_KEY JWT signing key, minimum 32 characters
JWT_ISSUER JWT issuer claim (default: littlehelpers)
JWT_AUDIENCE JWT audience claim (default: littlehelpers)
JWT_ACCESS_TOKEN_LIFETIME_HOURS Access token lifetime in hours (default: 168)
JWT_RENEW_TOKEN_LIFETIME_HOURS Renewed token lifetime in hours (default: 336)
SEED_ADMIN_PASSWORD Password for the initial admin account
MONTHLY_CYCLE_BREAKPOINT_DAY Day in month when a new allowance cycle starts (1-31, default: 1; falls back to last day when needed)
WEB_PORT Host port the frontend listens on (default: 80)
WEBAPP_NAME PWA name in manifest (default: LittleHelpers)
WEBAPP_SHORT_NAME PWA short_name in manifest (default: LittleHelpers)
WEBAPP_DESCRIPTION PWA description in manifest
WEBAPP_LANG Language used in manifest and <html lang> (default: en)
WEBAPP_TITLE Browser tab title (<title>) (default: LittleHelpers)
APISERVICE_IMAGE Override API image (optional, default: littlehelpers-api:latest)
WEBFRONTEND_IMAGE Override frontend image (optional, default: littlehelpers-web:latest)

Never commit .env to version control. It is listed in .gitignore.

Build and start

# Build images locally and start all services
docker compose up -d --build

# Or pull pre-built images (if APISERVICE_IMAGE / WEBFRONTEND_IMAGE are set)
docker compose up -d

The frontend will be available at http://localhost (or whatever WEB_PORT is set to). The API is not exposed to the host – it is only reachable from within the Docker network via the frontend nginx proxy.

Common commands

# View logs
docker compose logs -f

# Logs for a single service
docker compose logs -f apiservice

# Stop all services (data volume is preserved)
docker compose down

# Stop and remove all data (destructive)
docker compose down -v

# Restart a single service after a rebuild
docker compose up -d --build apiservice

Authentication

The API seeds a default admin account at startup if it does not already exist:

Field Value
Username admin
Password Value of SeedAdminPassword
Role Parent

Log in at /login in the frontend, or call POST /api/auth/login directly:

curl -X POST http://localhost/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"YourAdminPassword123!"}'

The response includes a JWT Bearer token. All endpoints except /api/auth/login and /api/health require the Authorization: Bearer <token> header.

To renew an active token, call:

curl -X POST http://localhost/api/auth/renew \
  -H "Authorization: Bearer <token>"

Database migrations

EF Core migrations are applied automatically at startup. To run them manually or create new ones:

# Apply migrations
dotnet ef database update \
  --project LittleHelpers.ApiService \
  --connection "Host=localhost;Port=5432;Username=littlehelpers;Password=secret;Database=littlehelpers"

# Create a new migration
dotnet ef migrations add <MigrationName> \
  --project LittleHelpers.ApiService \
  --output-dir Data/Migrations

The design-time factory (AppDbContextFactory) reads the connection string from the LITTLEHELPERS_CONNSTR environment variable when running dotnet ef commands outside of Aspire.


Running tests

dotnet test

All tests are in LittleHelpers.Tests/. The test suite uses an in-memory JWT key and does not require a running database or Docker.


Releases (GitHub tag-driven)

Releases are created automatically by GitHub Actions when you push a version tag that points to a commit on main.

Tag formats

Tag example Type GitHub release Docker tags
v0.0.7 Stable release Release v0.0.7 + latest
v0.0.7-pre1 Pre-release Pre-release v0.0.7-pre1 + prerelease

What happens automatically

  1. CI tests run first (.NET + Angular build/tests).
  2. Docker images are built and pushed (ferenyl/littlehelpers.api and ferenyl/littlehelpers.web).
  3. A GitHub release is created from the tag.
  4. Release notes are auto-generated from commits since the previous release and include GitHub compare links.

Create a stable release

git checkout main
git pull --ff-only
git tag v0.0.7
git push origin v0.0.7

Create a pre-release

git checkout main
git pull --ff-only
git tag v0.0.7-pre1
git push origin v0.0.7-pre1

If a tag does not point to a commit contained in main, release/publish jobs are skipped.


Translations (i18n)

The frontend uses @jsverse/transloco for internationalization. Supported languages are English (default) and Swedish.

How it works

On startup, the app detects the browser language via navigator.language. If the language starts with sv, Swedish is used – otherwise English is the fallback.

Translation files are static JSON files served as regular assets:

LittleHelpers.Web/public/i18n/
├── en.json   # English (default)
└── sv.json   # Swedish

The files are fetched at runtime via HttpClient. There is no build step required when adding or editing translations.

Key structure

Translations are organized by feature:

Prefix Covers
common.* Shared labels: loading, save, cancel, edit, delete, points
nav.* App name, logout button
login.* Login form and error messages
users.* User list, user form, role labels
chores.* Chore list, chore form, difficulty levels
children.* Children list view
childDetail.* Child detail view, allowance section, chart
months.1–12 Month names (used in chart/history labels)

Adding a new language

  1. Create public/i18n/<code>.json using en.json as a template.
  2. Register the language code in app.config.ts:
// in provideTransloco({ ... })
availableLangs: ['en', 'sv', '<code>'],
  1. Update detectLanguage() if you want the new language to be auto-detected:
function detectLanguage(): string {
  const lang = navigator.language.split('-')[0];
  return ['en', 'sv', '<code>'].includes(lang) ? lang : 'en';
}

Using translations in components

In templates, use the transloco pipe:

<p>{{ 'common.save' | transloco }}</p>

<!-- With interpolation parameters -->
<h2>{{ 'childDetail.allowance.title' | transloco: { month: translatedMonth(), year: year() } }}</h2>

In TypeScript (e.g. confirm dialogs), inject the service:

private transloco = inject(TranslocoService);

if (!confirm(this.transloco.translate('chores.confirmDelete'))) return;

Security notes

  • JWT key: stored in appsettings.Development.json locally (gitignored). In production, set via the Jwt__Key environment variable.
  • Seed password: stored in appsettings.Development.json locally (gitignored). In production, set via SeedAdminPassword.
  • Database credentials: never hardcoded. Always supplied via environment variables or Aspire connection strings.
  • The appsettings.Development.json file is gitignored. Do not commit it.

About

A tool for your family to help your kid to see how effort can make them money

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages