Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9eca43f
Add PocketBase migration plan and spike
kroqdotdev Apr 19, 2026
b56f09c
Add PocketBase test harness and CI pipeline
kroqdotdev Apr 19, 2026
c2f268f
Add full PocketBase schema migration
kroqdotdev Apr 19, 2026
638e725
Port tags, locations, and customFields to PocketBase
kroqdotdev Apr 19, 2026
e9690a9
Port remaining catalog domains to PocketBase
kroqdotdev Apr 19, 2026
3e63dbb
Port assets + assetTags domain to PocketBase
kroqdotdev Apr 19, 2026
4d53d64
Port asset search and dashboard queries
kroqdotdev Apr 19, 2026
67c4b6f
Port service schedules and records to PocketBase
kroqdotdev Apr 19, 2026
0d4b9fd
Update migration plan with current status
kroqdotdev Apr 20, 2026
8694203
Port users domain to PocketBase
kroqdotdev Apr 20, 2026
af709e9
Add PocketBase auth session layer + auth API routes
kroqdotdev Apr 20, 2026
396980f
Add API route handlers for all ported domains
kroqdotdev Apr 20, 2026
4ec9c62
Update migration plan with API routes + auth status
kroqdotdev Apr 20, 2026
cda6dc3
Port attachments + optimization pipeline to PocketBase
kroqdotdev Apr 20, 2026
b152662
Update plan: attachments done, frontend refactor is the last chunk
kroqdotdev Apr 20, 2026
3881a6c
Add client API foundation for the frontend refactor
kroqdotdev Apr 20, 2026
a28b73d
Swap auth shell onto PocketBase endpoints
kroqdotdev Apr 20, 2026
90f187a
Add typed client wrappers for every /api/** resource
kroqdotdev Apr 20, 2026
a5f3719
Convert categories + tags pages to TanStack Query + /api/**
kroqdotdev Apr 20, 2026
5836629
Convert locations page + useAppDateFormat to TanStack Query
kroqdotdev Apr 20, 2026
5266a7b
Convert fields page to TanStack Query; propagate FieldDefinition id
kroqdotdev Apr 20, 2026
7fc29bd
Update migration plan: frontend refactor in flight
kroqdotdev Apr 20, 2026
bdf192f
Port assets UI to PocketBase + TanStack Query
kroqdotdev Apr 20, 2026
4425ce0
Port dashboard UI to PocketBase + TanStack Query
kroqdotdev Apr 20, 2026
7f3ff20
Port global search to PocketBase + TanStack Query
kroqdotdev Apr 20, 2026
3028ba5
Port attachments UI to PocketBase + TanStack Query
kroqdotdev Apr 20, 2026
3e2705b
Port settings pages to PocketBase + TanStack Query
kroqdotdev Apr 20, 2026
60aca2f
Port labels UI to PocketBase + TanStack Query
kroqdotdev Apr 20, 2026
773aa6b
Port services UI to PocketBase + TanStack Query
kroqdotdev Apr 20, 2026
1ecc7a8
Remove ConvexClientProvider and final Convex client imports
kroqdotdev Apr 20, 2026
d8d6096
Remove convex directory and finish migration merge gate
kroqdotdev Apr 20, 2026
d8b4d41
Pivot to self-hosted only: deploy, docs, e2e
kroqdotdev Apr 20, 2026
1fcbcdf
Cover routes, hooks, api-client; fix e2e brittleness
kroqdotdev Apr 20, 2026
4531ba7
Fix migration regressions surfaced by docker-compose e2e run
kroqdotdev Apr 20, 2026
d662598
Update migration plan with final status
kroqdotdev Apr 21, 2026
69462a0
Finalize PocketBase migration hardening
kroqdotdev Apr 21, 2026
069bbb7
Fix and harden CI workflow
kroqdotdev Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 4 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ node_modules
.git
.env*
!.env.example
bin
coverage
playwright-report
test-results
docs
e2e
src/__tests__
convex/__tests__
src/server/**/__tests__
pb_data
*.md
.DS_Store
.vercel
.playwright-mcp
mprocs.log
mprocs.yaml
vitest.config.ts
vitest.pb.config.ts
playwright.config.ts
46 changes: 41 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,44 @@
# Convex deployment (run `npx convex dev` to get these)
CONVEX_DEPLOYMENT=
CONVEX_URL=
CONVEX_SITE_URL=
# PocketBase / app wiring
#
# URL the Next.js server uses to talk to PocketBase.
# - Local dev: usually http://127.0.0.1:8090
# - Docker Compose: usually http://pocketbase:8090
POCKETBASE_URL=http://127.0.0.1:8090

# E2E tests (email/password of a test user on your Convex instance)
# URL the browser uses to reach PocketBase directly.
# Used for browser-side PocketBase access such as realtime subscriptions.
# In production this should be the public, browser-reachable URL for PB
# (often behind the same reverse proxy/domain as the app).
NEXT_PUBLIC_POCKETBASE_URL=http://127.0.0.1:8090

# PocketBase superuser credentials.
# Required for:
# - the app's server-side admin PocketBase client
# - `pnpm pb:bootstrap`
# Set both before first boot/deploy.
POCKETBASE_SUPERUSER_EMAIL=dev@stowage.local
POCKETBASE_SUPERUSER_PASSWORD=

# Optional global attachment storage quota, in gigabytes.
# Examples: 0.5, 2, 10
# Behavior:
# - Leave blank to disable the quota entirely
# - Applies to both asset attachments and service-record attachments
# - Uses base-2 GB (1 GB = 1024 * 1024 * 1024 bytes)
# - Failed attachment uploads do not count toward the used total
STORAGE_LIMIT_GB=

# Optional public app base URL.
# Used when generating URLs embedded in labels / QR codes.
# Example local value: http://localhost:3000
# Example production value: https://assets.example.com
# Leave blank if you do not want a label URL base configured.
SITE_URL=http://localhost:3000

# E2E tests
#
# Optional credentials for a real user/admin on the target PocketBase instance.
# When these are unset, Playwright still runs the unauthenticated redirect
# coverage, but it skips the authenticated CRUD / workflow scenarios.
E2E_AUTH_EMAIL=
E2E_AUTH_PASSWORD=
52 changes: 52 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: CI

on:
push:
branches: [main, pocketbase-migration]
pull_request:
branches: [main, pocketbase-migration]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
checks:
name: Lint + typecheck + tests
runs-on: ubuntu-latest
timeout-minutes: 20
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

steps:
- uses: actions/checkout@v6

- uses: pnpm/action-setup@v5
with:
run_install: false

- uses: actions/setup-node@v6
with:
node-version: 20
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Lint
run: pnpm lint

- name: Typecheck
run: pnpm typecheck

- name: Vitest (jsdom components)
run: pnpm test

- name: Download PocketBase binary
run: pnpm pb:setup

- name: Vitest (PocketBase domain)
run: pnpm test:pb
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

# dependencies
/node_modules
/bin
/pb_data
/.pnp
.pnp.*
.yarn/*
Expand Down Expand Up @@ -44,9 +46,6 @@ yarn-error.log*
next-env.d.ts
import-meta.d.ts

# convex generated
convex/_generated/

# dev artifacts
mprocs.log
.playwright-mcp/
9 changes: 6 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ Open an issue before starting work on anything non-trivial. This saves everyone

1. Fork and clone the repo
2. `pnpm install`
3. run `npx convex dev` and connect your convex account (free tier is avalible and quite generous)
4. `pnpm dev` to start the dev server (runs Next.js + Convex via mprocs)
3. `pnpm pb:setup` — downloads the PocketBase binary into `./bin`
4. `cp .env.example .env.local` and fill in `POCKETBASE_SUPERUSER_EMAIL` + `POCKETBASE_SUPERUSER_PASSWORD`
5. `pnpm dev` — starts Next.js + PocketBase side-by-side via mprocs
6. Open <http://localhost:3000/setup> and create your first admin

## Before submitting a PR

- `pnpm typecheck` passes
- `pnpm lint` passes
- `pnpm test` passes
- `pnpm test` passes (jsdom suite)
- `pnpm test:pb` passes (PocketBase domain suite)
- `pnpm format:check` passes (or run `pnpm format` to fix)

## Code style
Expand Down
11 changes: 6 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# Stage 1: Install dependencies
# syntax=docker/dockerfile:1.6

# Stage 1 — install deps
FROM node:22-alpine AS deps
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

# Stage 2: Build
# Stage 2 — build the Next.js app (standalone output)
FROM node:22-alpine AS builder
WORKDIR /app
RUN corepack enable
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_PUBLIC_CONVEX_URL=https://placeholder.convex.cloud
RUN pnpm build

# Stage 3: Runtime
# Stage 3 — runtime
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
Expand All @@ -25,5 +26,5 @@ COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
USER nextjs
EXPOSE 3000
ENV HOSTNAME=0.0.0.0
ENV HOSTNAME=0.0.0.0 PORT=3000
CMD ["node", "server.js"]
28 changes: 28 additions & 0 deletions Dockerfile.pocketbase
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# syntax=docker/dockerfile:1.6

# Stage 1 — download the pinned PocketBase binary
FROM alpine:3.20 AS download
ARG PB_VERSION=0.37.1
RUN apk add --no-cache ca-certificates curl unzip
RUN set -eux; \
case "$(uname -m)" in \
x86_64) PB_ARCH=amd64 ;; \
aarch64) PB_ARCH=arm64 ;; \
*) echo "Unsupported arch: $(uname -m)" >&2; exit 1 ;; \
esac; \
curl -fsSL -o /tmp/pb.zip \
"https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_${PB_ARCH}.zip"; \
unzip -q /tmp/pb.zip -d /usr/local/bin; \
chmod +x /usr/local/bin/pocketbase

# Stage 2 — lean runtime image
FROM alpine:3.20
RUN apk add --no-cache ca-certificates wget
COPY --from=download /usr/local/bin/pocketbase /usr/local/bin/pocketbase
COPY pb_migrations /pb_migrations
VOLUME ["/pb_data"]
EXPOSE 8090
CMD ["pocketbase", "serve", \
"--http=0.0.0.0:8090", \
"--dir=/pb_data", \
"--migrationsDir=/pb_migrations"]
51 changes: 39 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,62 @@ Self-hosted asset management for small teams. Track physical assets with custom
## Tech stack

- **Frontend:** Next.js 16 (App Router, Turbopack), React 19, Tailwind CSS 4, shadcn/ui
- **Backend:** Convex (realtime database, file storage, auth)
- **Auth:** Convex Auth (email/password)
- **Labels:** bwip-js for barcode/data matrix generation, CSS @media print
- **Backend:** PocketBase (SQLite, auth, file storage, realtime subscriptions)
- **Labels:** bwip-js for barcode/data-matrix generation, CSS `@media print`

## Getting started
## Run locally

You need [Node.js](https://nodejs.org/) (v20+) and [pnpm](https://pnpm.io/).
You need [Node.js](https://nodejs.org/) 20+, [pnpm](https://pnpm.io/), and bash + curl (for the PocketBase setup script).

```bash
pnpm install
pnpm pb:setup # downloads the pocketbase binary into ./bin
cp .env.example .env.local # then set POCKETBASE_SUPERUSER_EMAIL + _PASSWORD
pnpm dev # runs Next.js + PocketBase side-by-side via mprocs
```

Copy `.env.example` to `.env.local` and fill in your Convex credentials. If you don't have a Convex project yet, `npx convex dev` will walk you through creating one.
Then open <http://localhost:3000/setup> and create the first admin account.

`pnpm pb:bootstrap` upserts a PocketBase superuser from the env vars — run it once after editing credentials, or whenever you rotate them.

## Run with Docker

```bash
pnpm dev
cp .env.example .env # fill in POCKETBASE_SUPERUSER_EMAIL / _PASSWORD
docker compose up --build
```

This starts both Next.js and Convex dev server via [mprocs](https://github.com/pvolok/mprocs).
`docker-compose.yml` brings up two services:

- `pocketbase` — PB 0.37 with `./pb_data` mounted as a volume, served on port `8090`
- `app` — the Next.js standalone build, served on port `3000`, pointed at the PB service over the compose network

Open <http://localhost:3000/setup> to create the first admin.

### Production deployment

The browser talks to PocketBase directly for realtime subscriptions, so for a public deployment put both services behind a reverse proxy on a single domain (Caddy, nginx, Traefik). Set `NEXT_PUBLIC_POCKETBASE_URL` to the proxy URL the browser should use (e.g. `https://stowage.example.com/pb`), and `POCKETBASE_URL` to the internal address Next uses to reach PB (e.g. `http://pocketbase:8090`). `pb_data/` is the only stateful directory — back it up.

Environment variables:

| Var | Required | Notes |
| ------------------------------------------ | -------- | ------------------------------------------------------- |
| `POCKETBASE_URL` | yes | Server-to-PB URL (e.g. `http://pocketbase:8090`) |
| `NEXT_PUBLIC_POCKETBASE_URL` | yes | Browser-to-PB URL (same origin as the app in prod) |
| `POCKETBASE_SUPERUSER_EMAIL` / `_PASSWORD` | yes | Used by `pnpm pb:bootstrap` and the app's admin client |
| `STORAGE_LIMIT_GB` | no | Global cap on total attachment bytes; omit for no limit |
| `SITE_URL` | no | Base URL embedded in label QR codes |

## Tests

```bash
pnpm test # unit tests (vitest)
pnpm test:e2e # end-to-end tests (playwright)
pnpm test:all # both
pnpm test # vitest jsdom suite (components, hooks, lib)
pnpm test:pb # vitest against a throwaway PocketBase instance (domain layer)
pnpm test:e2e # Playwright — boots PB + Next automatically
pnpm test:all # all three
```

E2E tests require `E2E_AUTH_EMAIL` and `E2E_AUTH_PASSWORD` in your `.env.local`.
E2E tests can pick up `E2E_AUTH_EMAIL` / `E2E_AUTH_PASSWORD` for the positive login path; without them, the auth flow tests fall back to the redirect-only checks.

## License

Expand Down
90 changes: 0 additions & 90 deletions convex/README.md

This file was deleted.

Loading