Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Database
DATABASE_PATH=./data/workout-tracker.db
# No configuration required for local development.
# The database is automatically created at ./data/workout-tracker.db
8 changes: 1 addition & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ jobs:

- name: Build
run: npm run build
env:
DATABASE_PATH: ./data/build.db

- name: E2E tests
run: |
mkdir -p data
npm run test:e2e
env:
DATABASE_PATH: ./data/test.db
run: npm run test:e2e
8 changes: 2 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,7 @@ npm run db:studio # Open Drizzle Studio

## Environment

Requires `DATABASE_PATH` env var. Copy `.env.example` to `.env` for local dev:

```
DATABASE_PATH=./data/workout-tracker.db
```
No environment variables required. The database is automatically created at `./data/workout-tracker.db` for local dev, or `/data/workout-tracker.db` when running in Docker (detected by the presence of `/data/`).

## Vitest Configuration

Expand All @@ -57,7 +53,7 @@ Any documentation written by agents must be clear, simple, and short. No unneces
## Architecture

- **DB schema**: `src/lib/server/db/schema.ts` — Drizzle schema definition
- **DB connection**: `src/lib/server/db/index.ts` — creates connection using `DATABASE_PATH`
- **DB connection**: `src/lib/server/db/index.ts` — creates connection with auto-detected path
- **Drizzle config**: `drizzle.config.ts` — migration config, schema path, SQLite dialect
- **Shared utils**: `src/lib/utils.ts` — `cn()` helper for Tailwind class merging + shadcn type helpers
- **Routes**: `src/routes/` — SvelteKit file-based routing
Expand Down
8 changes: 2 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,7 @@ npm run db:studio # Open Drizzle Studio

## Environment

Requires `DATABASE_PATH` env var. Copy `.env.example` to `.env` for local dev:

```
DATABASE_PATH=./data/workout-tracker.db
```
No environment variables required. The database is automatically created at `./data/workout-tracker.db` for local dev, or `/data/workout-tracker.db` when running in Docker (detected by the presence of `/data/`).

## Vitest Configuration

Expand All @@ -57,7 +53,7 @@ Any documentation written by agents must be clear, simple, and short. No unneces
## Architecture

- **DB schema**: `src/lib/server/db/schema.ts` — Drizzle schema definition
- **DB connection**: `src/lib/server/db/index.ts` — creates connection using `DATABASE_PATH`
- **DB connection**: `src/lib/server/db/index.ts` — creates connection with auto-detected path
- **Drizzle config**: `drizzle.config.ts` — migration config, schema path, SQLite dialect
- **Shared utils**: `src/lib/utils.ts` — `cn()` helper for Tailwind class merging + shadcn type helpers
- **Routes**: `src/routes/` — SvelteKit file-based routing
Expand Down
1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ RUN npm install

# Copy source and build
COPY . .
ENV DATABASE_PATH=/tmp/build.db
RUN npm run build

# Stage 2: Production
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ A self-hosted, mobile-first workout logging app with progressive overload tracki
## Requirements

- Node.js 22+
- `DATABASE_PATH` environment variable pointing to a SQLite database file

## Development

```sh
cp .env.example .env
npm install
npm run dev
```

The database is automatically created at `./data/workout-tracker.db`.

## Deployment with Docker

### Using Docker Compose (recommended)
Expand All @@ -23,15 +23,14 @@ npm run dev
docker compose up -d
```

This builds the image, starts the container on port 3000, and persists the database in a named volume (`workout-data`).
This starts the container on port 6789 and persists the database via a volume mount to `/data`.

### Using Docker directly

```sh
docker build -t workout-tracker .
docker run -d \
-p 3000:3000 \
-e DATABASE_PATH=/data/workout-tracker.db \
-v workout-data:/data \
workout-tracker
```
Expand All @@ -41,15 +40,18 @@ docker run -d \
```sh
docker run -d \
-p 3000:3000 \
-e DATABASE_PATH=/data/workout-tracker.db \
-v workout-data:/data \
ghcr.io/kavith-k/workout-tracker:latest
```

### Environment variables
### Data persistence

The database is stored at `/data/workout-tracker.db` inside the container. Mount a volume to `/data` to persist data across container restarts.

### Configuration

No environment variables are required. The app is zero-config.

| Variable | Required | Description |
| --------------- | -------- | --------------------------------------------------------------- |
| `DATABASE_PATH` | Yes | Path to SQLite database file (e.g., `/data/workout-tracker.db`) |
### HTTPS

The database file and directory are created automatically on first run.
Service workers and offline features require HTTPS in most browsers. This works automatically on `localhost`, but remote access over plain HTTP will disable offline support. Use a reverse proxy (e.g., Caddy, Traefik, nginx) to terminate TLS if accessing the app over the network.
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ services:
container_name: workout-tracker
ports:
- '6789:3000'
environment:
- DATABASE_PATH=/data/workout-tracker.db
volumes:
- docker-config/workout-tracker:/data
restart: unless-stopped
4 changes: 1 addition & 3 deletions drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { defineConfig } from 'drizzle-kit';

if (!process.env.DATABASE_PATH) throw new Error('DATABASE_PATH is not set');

export default defineConfig({
schema: './src/lib/server/db/schema.ts',
dialect: 'sqlite',
dbCredentials: { url: process.env.DATABASE_PATH },
dbCredentials: { url: './data/workout-tracker.db' },
verbose: true,
strict: true
});
5 changes: 3 additions & 2 deletions e2e/exercise-library.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ test.describe.serial('Exercise Library', () => {
// Delete dialog should appear — no workout history so should not mention it
const dialogDescription = page.locator('[role="alertdialog"]');
await expect(dialogDescription).toBeVisible();
await expect(dialogDescription).toContainText('Are you sure you want to delete');
await expect(dialogDescription).not.toContainText('workout history');
await expect(dialogDescription).toContainText('will be removed from any programs');
await expect(dialogDescription).toContainText('cannot be undone');
await expect(dialogDescription).not.toContainText('History will be kept');

// Confirm delete
await page.getByRole('button', { name: 'Delete' }).click();
Expand Down
2 changes: 1 addition & 1 deletion e2e/global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Database from 'better-sqlite3';
import fs from 'node:fs';
import path from 'node:path';

const DB_PATH = './data/e2e-test.db';
const DB_PATH = './data/workout-tracker.db';

export default function globalSetup() {
// Remove existing test database for a clean slate
Expand Down
42 changes: 1 addition & 41 deletions e2e/history.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { expect, test } from '@playwright/test';

test.describe.serial('History', () => {
test('shows history by date with completed workouts', async ({ page }) => {
test('shows history with completed workouts', async ({ page }) => {
await page.goto('/history');

await expect(page.getByRole('heading', { name: 'History' })).toBeVisible();
await expect(page.getByTestId('view-toggle')).toBeVisible();

// Should have session cards from previous workout-flow tests
const sessionCards = page.getByTestId('session-card');
Expand Down Expand Up @@ -33,30 +32,6 @@ test.describe.serial('History', () => {
await expect(page.getByText('Skipped').first()).toBeVisible();
});

test('toggles between by-date and by-exercise views', async ({ page }) => {
await page.goto('/history');

await expect(page.getByTestId('view-toggle')).toBeVisible();

// Click "By Exercise" to switch views
await page.getByText('By Exercise').click();
await expect(page).toHaveURL('/history/by-exercise');

// Should show exercise history items
const exerciseItems = page.getByTestId('exercise-history-item');
const count = await exerciseItems.count();
expect(count).toBeGreaterThan(0);

// Each item should show session count
await expect(exerciseItems.first().getByTestId('exercise-session-count')).toBeVisible();
await expect(exerciseItems.first().getByTestId('exercise-last-performed')).toBeVisible();

// Click "By Date" to switch back
await page.getByText('By Date').click();
await expect(page).toHaveURL('/history');
await expect(page.getByTestId('session-card').first()).toBeVisible();
});

test('views session detail with exercise logs and sets', async ({ page }) => {
await page.goto('/history');

Expand Down Expand Up @@ -129,19 +104,4 @@ test.describe.serial('History', () => {
// Wait for the session to be removed (use auto-retrying assertion)
await expect(sessionCards).toHaveCount(countBefore - 1);
});

test('deleted data no longer appears in by-exercise view', async ({ page }) => {
// Navigate to by-exercise view to verify consistency after deletions
await page.goto('/history/by-exercise');

// The view should still work and show exercises
await expect(page.getByTestId('view-toggle')).toBeVisible();

// Exercise items should have valid session counts
const exerciseItems = page.getByTestId('exercise-history-item');
const count = await exerciseItems.count();
if (count > 0) {
await expect(exerciseItems.first().getByTestId('exercise-session-count')).toBeVisible();
}
});
});
6 changes: 4 additions & 2 deletions e2e/program-management.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,12 @@ test.describe.serial('Program Management', () => {
await page.getByRole('menuitem', { name: 'Delete' }).click();

// Delete confirmation dialog should appear
await expect(page.getByText('Are you sure you want to delete')).toBeVisible();
const deleteDialog = page.locator('[role="alertdialog"]');
await expect(deleteDialog).toBeVisible();
await expect(deleteDialog).toContainText('cannot be undone');

// Confirm delete
await page.getByRole('button', { name: 'Delete' }).click();
await deleteDialog.getByRole('button', { name: 'Delete' }).click();

// Program count should decrease
await expect(page.locator('[data-testid="program-card"]')).toHaveCount(cardsBefore - 1);
Expand Down
Loading