Compare commits

..

11 Commits

Author SHA1 Message Date
cartsnitch-ci[bot] d41e8159d0 fix(api): use repo-root-relative paths in Dockerfile for context: .
The CI build-and-push-api job uses context: . (repo root), so all
COPY paths must be relative to the repo root, not the api/ directory.

Also installs ./common/ alongside the api package, matching the
receiptwitness Dockerfile pattern.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 00:46:59 +00:00
Barcode Betty f23e2786e8 feat(ci): add build-and-push-api job for ghcr.io/cartsnitch/api
- Add API_IMAGE_NAME env var
- Add build-and-push-api job: context=. file=./api/Dockerfile
- Add to deploy-dev needs; update kustomize image edit and commit message

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 00:46:59 +00:00
Barcode Betty ae3592b22f fix: update stale package-lock.json to resolve npm ci failure
package.json references packages (better-auth@1.5.6, etc.) not present
in the lock file, causing npm ci to fail on CI. Regenerate the lock file
so CI can install dependencies correctly.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 00:46:59 +00:00
Barcode Betty f9d0bd5b79 feat: add MSW for integration test mocking
Install Mock Service Worker (MSW) and configure it for vitest.
Write one integration test for usePurchases hook using MSW.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 00:46:59 +00:00
Barcode Betty a8fd9c0c3f chore: remove polyrepo CI workflow leftovers
Delete nested .github/workflows/ci.yml files from api/ and receiptwitness/
directories. These workflows were from the polyrepo era and reference the
deleted cartsnitch/common repo. They do not execute as GitHub Actions (not
at repo root) and are confusing.

No functional change — the monorepo CI is defined at .github/workflows/.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 00:46:59 +00:00
Barcode Betty 97a3ec6de6 feat(ci): add receiptwitness build job to monorepo CI 2026-03-31 00:46:59 +00:00
Barcode Betty 0dd0c49f4d feat(ci): add receiptwitness build job to monorepo CI 2026-03-31 00:46:59 +00:00
cartsnitch-engineer[bot] 2ccdef5c90 docs: add UAT runbook v1
Merges docs/uat-runbook into main. UAT Runbook v1 authored by Savannah Savings (CTO). QA and CTO approved.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 00:46:59 +00:00
Barcode Betty 04f0a34908 feat: add utility functions with unit tests
Add formatCurrency, formatDate, and storeSlugs utilities in src/utils/
with 21 vitest unit tests covering standard and edge cases.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 00:46:59 +00:00
Barcode Betty 2b50aad682 fix(deploy): include alembic in API Docker image
Adds alembic.ini and alembic/ directory to the production API image so
alembic upgrade head can run in-cluster as an init container.

Also carries migration 003 (make hashed_password nullable) from PR #66.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 00:46:59 +00:00
Barcode Betty 2c64ab9e98 fix: use same-origin default for auth URL instead of localhost
Avoids ERR_CONNECTION_REFUSED in deployed environments where
VITE_AUTH_URL is not set at build time. Empty-string fallback
routes auth requests to same origin, which the HTTPRoute forwards
to the auth service.

cc @cpfarhood

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 00:46:59 +00:00
3 changed files with 60 additions and 36 deletions
+52 -2
View File
@@ -19,6 +19,7 @@ env:
IMAGE_NAME: cartsnitch/cartsnitch IMAGE_NAME: cartsnitch/cartsnitch
AUTH_IMAGE_NAME: cartsnitch/auth AUTH_IMAGE_NAME: cartsnitch/auth
RECEIPTWITNESS_IMAGE_NAME: cartsnitch/receiptwitness RECEIPTWITNESS_IMAGE_NAME: cartsnitch/receiptwitness
API_IMAGE_NAME: cartsnitch/api
jobs: jobs:
lint: lint:
@@ -210,9 +211,57 @@ jobs:
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-and-push-api:
runs-on: runners-cartsnitch
needs: [lint, test]
outputs:
calver_tag: ${{ steps.calver.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate CalVer tag
id: calver
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
DATE_TAG=$(date -u +%Y.%m.%d)
EXISTING=$(git tag -l "v${DATE_TAG}*" | sort -V | tail -1)
if [ -z "$EXISTING" ]; then VERSION="$DATE_TAG"
elif [ "$EXISTING" = "v${DATE_TAG}" ]; then VERSION="${DATE_TAG}.2"
else BUILD_NUM=$(echo "$EXISTING" | sed "s/v${DATE_TAG}\.//"); VERSION="${DATE_TAG}.$((BUILD_NUM + 1))"; fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Log in to GHCR
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (API)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.API_IMAGE_NAME }}
tags: |
type=sha,prefix=sha-
type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and push API Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./api/Dockerfile
push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
deploy-dev: deploy-dev:
runs-on: runners-cartsnitch runs-on: runners-cartsnitch
needs: [build-and-push, build-and-push-auth, build-and-push-receiptwitness] needs: [build-and-push, build-and-push-auth, build-and-push-receiptwitness, build-and-push-api]
if: github.event_name == 'push' && github.ref == 'refs/heads/main' if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps: steps:
- name: Generate GitHub App token - name: Generate GitHub App token
@@ -244,6 +293,7 @@ jobs:
kustomize edit set image ghcr.io/cartsnitch/cartsnitch:${{ needs.build-and-push.outputs.calver_tag }} kustomize edit set image ghcr.io/cartsnitch/cartsnitch:${{ needs.build-and-push.outputs.calver_tag }}
kustomize edit set image ghcr.io/cartsnitch/auth:${{ needs.build-and-push-auth.outputs.calver_tag }} kustomize edit set image ghcr.io/cartsnitch/auth:${{ needs.build-and-push-auth.outputs.calver_tag }}
kustomize edit set image ghcr.io/cartsnitch/receiptwitness:${{ needs.build-and-push-receiptwitness.outputs.calver_tag }} kustomize edit set image ghcr.io/cartsnitch/receiptwitness:${{ needs.build-and-push-receiptwitness.outputs.calver_tag }}
kustomize edit set image ghcr.io/cartsnitch/api:${{ needs.build-and-push-api.outputs.calver_tag }}
- name: Commit and push to infra - name: Commit and push to infra
run: | run: |
@@ -251,5 +301,5 @@ jobs:
git config user.name "cartsnitch-ci[bot]" git config user.name "cartsnitch-ci[bot]"
git config user.email "cartsnitch-ci[bot]@users.noreply.github.com" git config user.email "cartsnitch-ci[bot]@users.noreply.github.com"
git add apps/overlays/dev/kustomization.yaml git add apps/overlays/dev/kustomization.yaml
git commit -m "ci(dev): update cartsnitch, auth, and receiptwitness images" git commit -m "ci(dev): update cartsnitch, auth, receiptwitness, and api images"
git push origin main git push origin main
+8 -6
View File
@@ -5,19 +5,21 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \ build-essential \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Build context is the repo root. These paths are relative to the root.
WORKDIR /app WORKDIR /app
COPY pyproject.toml ./ COPY api/pyproject.toml ./
COPY src/ ./src/ COPY api/src/ ./src/
RUN pip install --no-cache-dir --prefix=/install . COPY common/ ./common/
RUN pip install --no-cache-dir --prefix=/install ./common/ .
FROM python:3.12-slim AS prod FROM python:3.12-slim AS prod
WORKDIR /app WORKDIR /app
RUN adduser --system --group --uid 1000 app RUN adduser --system --group --uid 1000 app
COPY --from=build /install /usr/local COPY --from=build /install /usr/local
COPY src/ ./src/ COPY api/src/ ./src/
COPY alembic.ini ./ COPY api/alembic.ini ./
COPY alembic/ ./alembic/ COPY api/alembic/ ./alembic/
USER 1000 USER 1000
EXPOSE 8000 EXPOSE 8000
-28
View File
@@ -1,36 +1,8 @@
import { createAuthClient } from "better-auth/react" import { createAuthClient } from "better-auth/react"
import type { BetterFetchPlugin } from "@better-fetch/fetch"
/**
* Maps 'name' -> 'display_name' in register requests to match the API's RegisterRequest schema.
*/
const displayNameMapper: BetterFetchPlugin = {
id: "display-name-mapper",
name: "display-name-mapper",
hooks: {
onRequest: async (context) => {
const url = typeof context.url === "string" ? context.url : context.url.pathname
if (
url.endsWith("/auth/register") &&
context.method === "POST" &&
context.body &&
"name" in context.body
) {
context.body = {
...context.body,
display_name: context.body.name as string,
name: undefined,
}
}
return context
},
},
}
export const authClient = createAuthClient({ export const authClient = createAuthClient({
baseURL: import.meta.env.VITE_AUTH_URL || "", baseURL: import.meta.env.VITE_AUTH_URL || "",
basePath: "/auth", basePath: "/auth",
fetchPlugins: [displayNameMapper],
}) })
export const { useSession, signIn, signUp, signOut } = authClient export const { useSession, signIn, signUp, signOut } = authClient