Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cfa4d8fa91 | |||
| 39e8d5c9f9 | |||
| 44c475265e | |||
| 8e1f61214c | |||
| 75be08ccf3 |
@@ -179,20 +179,24 @@ jobs:
|
|||||||
repository: cartsnitch/infra
|
repository: cartsnitch/infra
|
||||||
token: ${{ steps.app-token.outputs.token }}
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
ref: main
|
ref: main
|
||||||
|
path: infra
|
||||||
|
|
||||||
- name: Install kubectl
|
- name: Install kubectl
|
||||||
uses: azure/setup-kubectl@v4
|
uses: azure/setup-kubectl@v4
|
||||||
|
|
||||||
|
- name: Install kustomize
|
||||||
|
uses: imranismail/setup-kustomize@v2
|
||||||
|
|
||||||
- name: Update dev overlay image tag
|
- name: Update dev overlay image tag
|
||||||
working-directory: apps/overlays/dev
|
|
||||||
run: |
|
run: |
|
||||||
|
cd infra/apps/overlays/dev
|
||||||
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 }}
|
||||||
|
|
||||||
- name: Commit and push to infra
|
- name: Commit and push to infra
|
||||||
run: |
|
run: |
|
||||||
cd apps/overlays/dev
|
cd infra
|
||||||
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 kustomization.yaml
|
git add apps/overlays/dev/kustomization.yaml
|
||||||
git commit -m "ci(dev): update cartsnitch image to ${{ needs.build-and-push.outputs.calver_tag }}"
|
git commit -m "ci(dev): update cartsnitch image to ${{ needs.build-and-push.outputs.calver_tag }}"
|
||||||
git push origin main
|
git push origin main
|
||||||
|
|||||||
@@ -1,45 +1 @@
|
|||||||
# CartSnitch Monorepo
|
# CartSnitch
|
||||||
|
|
||||||
CartSnitch is a self-hosted grocery price intelligence platform. This repo consolidates the core services and the flagship frontend PWA.
|
|
||||||
|
|
||||||
## Services
|
|
||||||
|
|
||||||
| Directory | Service | Purpose |
|
|
||||||
|-----------|---------|---------|
|
|
||||||
| `/` (root) | **Frontend** | React 18 PWA — mobile-first price intelligence UI |
|
|
||||||
| `api/` | **API Gateway** | FastAPI — frontend-facing REST API |
|
|
||||||
| `common/` | **Common** | Shared Python models, schemas, Alembic migrations |
|
|
||||||
| `receiptwitness/` | **ReceiptWitness** | Purchase ingestion via retailer scrapers |
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Frontend (root)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
npm run dev # http://localhost:5173
|
|
||||||
npm run build # production build
|
|
||||||
npm run test # unit tests (Vitest)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Python Services
|
|
||||||
|
|
||||||
Each Python service uses [uv](https://github.com/astral-sh/uv) and has its own `pyproject.toml`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd api # or common / receiptwitness
|
|
||||||
uv sync
|
|
||||||
uv run pytest
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Workflow
|
|
||||||
|
|
||||||
- **Never push directly to main.** Always open a PR from a feature branch.
|
|
||||||
- Branch naming: `feature/<description>` or `fix/<description>`
|
|
||||||
- Conventional commits: `feat:`, `fix:`, `refactor:`, `docs:`, `chore:`
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
For full details see [CLAUDE.md](./CLAUDE.md) or the per-service `CLAUDE.md` in each subdirectory.
|
|
||||||
|
|
||||||
CartSnitch is a polyrepo-style monorepo: each service can be built and deployed independently, but sharing code between `common/` and the other Python services is done via local path dependencies in `pyproject.toml`.
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# CartSnitch Common
|
||||||
|
|
||||||
|
Shared models, schemas, and utilities for CartSnitch services.
|
||||||
|
|
||||||
|
## Test Users
|
||||||
|
|
||||||
|
The following users are seeded by `cartsnitch-seed` and can be used for local development and UAT.
|
||||||
|
|
||||||
|
| Email | Password | Display Name | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `uat@cartsnitch.com` | `CartSnitch-UAT-2026!` | UAT Tester | Primary UAT account. Use for regression testing in the CartSnitch frontend. Created by the seed runner via Better-Auth's bcrypt path — credentials work against the live auth service. Idempotent; re-running the seed skips this user if it already exists. |
|
||||||
|
|
||||||
|
### Running the Seed
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install with seed dependencies
|
||||||
|
pip install -e "cartsnitch-common[seed]"
|
||||||
|
|
||||||
|
# Run (requires CARTSNITCH_DATABASE_URL_SYNC)
|
||||||
|
CARTSNITCH_DATABASE_URL_SYNC=postgresql://user:pass@localhost:5432/cartsnitch \
|
||||||
|
cartsnitch-seed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
- **Models** live in `src/cartsnitch_common/models/`
|
||||||
|
- **Alembic migrations** run via the `api` service (`api/alembic/`)
|
||||||
|
- **Seed runner** runs via `cartsnitch-seed` (installed as a package entry point)
|
||||||
@@ -27,6 +27,7 @@ dev = [
|
|||||||
]
|
]
|
||||||
seed = [
|
seed = [
|
||||||
"faker>=33.0,<34.0",
|
"faker>=33.0,<34.0",
|
||||||
|
"bcrypt>=4.0,<6.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
import uuid
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import bcrypt
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -184,6 +186,65 @@ def run_seed(
|
|||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
_seed_uat_user(session)
|
||||||
|
|
||||||
elapsed = time.monotonic() - t0
|
elapsed = time.monotonic() - t0
|
||||||
_log("")
|
_log("")
|
||||||
_log(f"Seed complete in {elapsed:.1f}s")
|
_log(f"Seed complete in {elapsed:.1f}s")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# UAT seed user
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UAT_EMAIL = "uat@cartsnitch.com"
|
||||||
|
UAT_PASSWORD = "CartSnitch-UAT-2026!"
|
||||||
|
UAT_DISPLAY_NAME = "UAT Tester"
|
||||||
|
UAT_USER_ID = uuid.UUID("00000000-0000-0000-0000-000000000001")
|
||||||
|
|
||||||
|
|
||||||
|
def _seed_uat_user(session: Session) -> None:
|
||||||
|
"""Insert or verify the dedicated UAT test user.
|
||||||
|
|
||||||
|
The user is created via Better-Auth's bcrypt hashing path so credentials
|
||||||
|
work against the live auth service. Idempotent — skips if the user already
|
||||||
|
exists.
|
||||||
|
"""
|
||||||
|
existing = session.execute(
|
||||||
|
text("SELECT id FROM users WHERE email = :email"),
|
||||||
|
{"email": UAT_EMAIL},
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
if existing is not None:
|
||||||
|
_log(f"UAT user {UAT_EMAIL} already exists — skipping")
|
||||||
|
return
|
||||||
|
|
||||||
|
password_hash = bcrypt.hashpw(UAT_PASSWORD.encode(), bcrypt.gensalt()).decode()
|
||||||
|
|
||||||
|
session.execute(
|
||||||
|
text(
|
||||||
|
"INSERT INTO users (id, email, hashed_password, display_name, email_verified, created_at, updated_at) "
|
||||||
|
"VALUES (:id, :email, :hashed_password, :display_name, true, now(), now())"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"id": str(UAT_USER_ID),
|
||||||
|
"email": UAT_EMAIL,
|
||||||
|
"hashed_password": password_hash,
|
||||||
|
"display_name": UAT_DISPLAY_NAME,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
session.execute(
|
||||||
|
text(
|
||||||
|
"INSERT INTO accounts (id, user_id, account_id, provider_id, password, created_at, updated_at) "
|
||||||
|
"VALUES (gen_random_uuid()::text, :user_id, :account_id, 'credential', :password, now(), now())"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"user_id": str(UAT_USER_ID),
|
||||||
|
"account_id": str(UAT_USER_ID),
|
||||||
|
"password": password_hash,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
_log(f"UAT user {UAT_EMAIL} created")
|
||||||
|
|||||||
Reference in New Issue
Block a user