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
8 changed files with 6 additions and 141 deletions
+2 -14
View File
@@ -48,21 +48,9 @@ jobs:
- name: Run tests - name: Run tests
run: npx vitest run run: npx vitest run
e2e:
runs-on: runners-cartsnitch
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx playwright test
build-and-push: build-and-push:
runs-on: runners-cartsnitch runs-on: runners-cartsnitch
needs: [lint, test, e2e] needs: [lint, test]
outputs: outputs:
calver_tag: ${{ steps.calver.outputs.version }} calver_tag: ${{ steps.calver.outputs.version }}
steps: steps:
@@ -124,7 +112,7 @@ jobs:
build-and-push-auth: build-and-push-auth:
runs-on: runners-cartsnitch runs-on: runners-cartsnitch
needs: [lint, test, e2e] needs: [lint, test]
outputs: outputs:
calver_tag: ${{ steps.calver.outputs.version }} calver_tag: ${{ steps.calver.outputs.version }}
steps: steps:
+3 -6
View File
@@ -1,5 +1,3 @@
# Stage 1: Build dependencies
# Build context is the repo root. Paths below are relative to the root.
FROM python:3.12-slim AS build FROM python:3.12-slim AS build
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -7,16 +5,15 @@ 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 api/pyproject.toml ./ COPY api/pyproject.toml ./
COPY api/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/ .
# Stage 2: Production image
FROM python:3.12-slim AS prod FROM python:3.12-slim AS prod
RUN apt-get update && apt-get install -y --no-install-recommends libpq5 && rm -rf /var/lib/apt/lists/*
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
-6
View File
@@ -1,6 +0,0 @@
import { test, expect } from '@playwright/test';
test('app loads', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/CartSnitch/);
});
-64
View File
@@ -18,7 +18,6 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.4", "@eslint/js": "^9.39.4",
"@playwright/test": "^1.49.0",
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.0.0",
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
@@ -2721,22 +2720,6 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@playwright/test": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.58.2"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@reduxjs/toolkit": { "node_modules/@reduxjs/toolkit": {
"version": "2.11.2", "version": "2.11.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz",
@@ -8189,53 +8172,6 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/playwright": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.58.2"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/playwright/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/possible-typed-array-names": { "node_modules/possible-typed-array-names": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
+1 -3
View File
@@ -9,8 +9,7 @@
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview", "preview": "vite preview",
"test": "NODE_ENV=test vitest run", "test": "NODE_ENV=test vitest run",
"test:watch": "NODE_ENV=test vitest", "test:watch": "NODE_ENV=test vitest"
"test:e2e": "npx playwright test"
}, },
"dependencies": { "dependencies": {
"@tanstack/react-query": "^5.0.0", "@tanstack/react-query": "^5.0.0",
@@ -33,7 +32,6 @@
"eslint": "^9.39.4", "eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2", "eslint-plugin-react-refresh": "^0.5.2",
"@playwright/test": "^1.49.0",
"globals": "^17.4.0", "globals": "^17.4.0",
"jsdom": "^25.0.1", "jsdom": "^25.0.1",
"msw": "^2.12.14", "msw": "^2.12.14",
-19
View File
@@ -1,19 +0,0 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
},
use: {
baseURL: 'http://localhost:5173',
},
});
-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
-1
View File
@@ -7,6 +7,5 @@ export default defineConfig({
environment: 'jsdom', environment: 'jsdom',
globals: true, globals: true,
setupFiles: ['./src/test/setup.ts'], setupFiles: ['./src/test/setup.ts'],
exclude: ['e2e/**', 'node_modules/**'],
}, },
}) })