Compare commits

...

2 Commits

Author SHA1 Message Date
Gitea Actions
1fcb9fd5c7 ci: Bump version to 0.12.6 [skip ci] 2026-01-22 03:41:25 +05:00
8bd4e081ea e2e fixin, frontend + home page work
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 19m0s
2026-01-21 14:40:19 -08:00
20 changed files with 194 additions and 82 deletions

154
CLAUDE.md
View File

@@ -424,11 +424,24 @@ psql -d "flyer-crawler-test" -c "\dn+ public"
The dev container runs its own **local Bugsink instance** - it does NOT connect to the production Bugsink server:
- **Local Bugsink**: Runs at `http://localhost:8000` inside the container
- **Pre-configured DSNs**: Set in `compose.dev.yml`, pointing to local instance
- **Local Bugsink UI**: Accessible at `https://localhost:8443` (proxied from `http://localhost:8000` by nginx)
- **Admin credentials**: `admin@localhost` / `admin`
- **Bugsink Projects**: Backend (Dev) - Project ID 1, Frontend (Dev) - Project ID 2
- **Configuration Files**:
- `compose.dev.yml` - Sets default DSNs using `127.0.0.1:8000` protocol (for initial container setup)
- `.env.local` - **OVERRIDES** compose.dev.yml with `localhost:8000` protocol (this is what the app actually uses)
- **CRITICAL**: `.env.local` takes precedence over `compose.dev.yml` environment variables
- **DSN Configuration**:
- **Backend DSN** (Node.js/Express): Configured in `.env.local` as `SENTRY_DSN=http://<key>@localhost:8000/1`
- **Frontend DSN** (React/Browser): Configured in `.env.local` as `VITE_SENTRY_DSN=http://<key>@localhost:8000/2`
- **Why localhost instead of 127.0.0.1?** The `.env.local` file was created separately and uses `localhost` which works fine in practice
- **HTTPS Setup**: Self-signed certificates auto-generated with mkcert on container startup (for UI access only, not for Sentry SDK)
- **CSRF Protection**: Django configured with `SECURE_PROXY_SSL_HEADER` to trust `X-Forwarded-Proto` from nginx
- **Isolated**: Dev errors stay local, don't pollute production/test dashboards
- **No Gitea secrets needed**: Everything is self-contained in the container
- **Accessing Errors**:
- **Via Browser**: Open `https://localhost:8443` and login to view issues
- **Via MCP**: Configure a second Bugsink MCP server pointing to `http://localhost:8000` (see MCP Servers section below)
---
@@ -436,64 +449,105 @@ The dev container runs its own **local Bugsink instance** - it does NOT connect
The following MCP servers are configured for this project:
| Server | Purpose |
| --------------------- | ------------------------------------------- |
| gitea-projectium | Gitea API for gitea.projectium.com |
| gitea-torbonium | Gitea API for gitea.torbonium.com |
| podman | Container management |
| filesystem | File system access |
| fetch | Web fetching |
| markitdown | Convert documents to markdown |
| sequential-thinking | Step-by-step reasoning |
| memory | Knowledge graph persistence |
| postgres | Direct database queries (localhost:5432) |
| playwright | Browser automation and testing |
| redis | Redis cache inspection (localhost:6379) |
| sentry-selfhosted-mcp | Error tracking via Bugsink (localhost:8000) |
| Server | Purpose |
| ------------------- | ---------------------------------------------------------------------------- |
| gitea-projectium | Gitea API for gitea.projectium.com |
| gitea-torbonium | Gitea API for gitea.torbonium.com |
| podman | Container management |
| filesystem | File system access |
| fetch | Web fetching |
| markitdown | Convert documents to markdown |
| sequential-thinking | Step-by-step reasoning |
| memory | Knowledge graph persistence |
| postgres | Direct database queries (localhost:5432) |
| playwright | Browser automation and testing |
| redis | Redis cache inspection (localhost:6379) |
| bugsink | Error tracking - production Bugsink (bugsink.projectium.com) - **PROD/TEST** |
| bugsink-dev | Error tracking - dev container Bugsink (localhost:8000) - **DEV CONTAINER** |
**Note:** MCP servers work in both **Claude CLI** and **Claude Code VS Code extension** (as of January 2026).
### Sentry/Bugsink MCP Server Setup (ADR-015)
**CRITICAL**: There are **TWO separate Bugsink MCP servers**:
To enable Claude Code to query and analyze application errors from Bugsink:
- **bugsink**: Connects to production Bugsink at `https://bugsink.projectium.com` for production and test server errors
- **bugsink-dev**: Connects to local dev container Bugsink at `http://localhost:8000` for local development errors
1. **Install the MCP server**:
### Bugsink MCP Server Setup (ADR-015)
```bash
# Clone the sentry-selfhosted-mcp repository
git clone https://github.com/ddfourtwo/sentry-selfhosted-mcp.git
cd sentry-selfhosted-mcp
npm install
```
**IMPORTANT**: You need to configure **TWO separate MCP servers** - one for production/test, one for local dev.
2. **Configure Claude Code** (add to `.claude/mcp.json`):
#### Installation (shared for both servers)
```json
{
"sentry-selfhosted-mcp": {
"command": "node",
"args": ["/path/to/sentry-selfhosted-mcp/dist/index.js"],
"env": {
"SENTRY_URL": "http://localhost:8000",
"SENTRY_AUTH_TOKEN": "<get-from-bugsink-ui>",
"SENTRY_ORG_SLUG": "flyer-crawler"
}
}
}
```
```bash
# Clone the bugsink-mcp repository (NOT sentry-selfhosted-mcp)
git clone https://github.com/j-shelfwood/bugsink-mcp.git
cd bugsink-mcp
npm install
npm run build
```
3. **Get the auth token**:
- Navigate to Bugsink UI at `http://localhost:8000`
- Log in with admin credentials
- Go to Settings > API Keys
- Create a new API key with read access
#### Production/Test Bugsink MCP (bugsink)
4. **Available capabilities**:
- List projects and issues
- View detailed error events
- Search by error message or stack trace
- Update issue status (resolve, ignore)
- Add comments to issues
Add to `.claude/mcp.json`:
```json
{
"bugsink": {
"command": "node",
"args": ["d:\\gitea\\bugsink-mcp\\dist\\index.js"],
"env": {
"BUGSINK_URL": "https://bugsink.projectium.com",
"BUGSINK_API_TOKEN": "<get-from-production-bugsink>",
"BUGSINK_ORG_SLUG": "sentry"
}
}
}
```
**Get the auth token**:
- Navigate to https://bugsink.projectium.com
- Log in with production credentials
- Go to Settings > API Keys
- Create a new API key with read access
#### Dev Container Bugsink MCP (bugsink-dev)
Add to `.claude/mcp.json`:
```json
{
"bugsink-dev": {
"command": "node",
"args": ["d:\\gitea\\bugsink-mcp\\dist\\index.js"],
"env": {
"BUGSINK_URL": "http://localhost:8000",
"BUGSINK_API_TOKEN": "<get-from-local-bugsink>",
"BUGSINK_ORG_SLUG": "sentry"
}
}
}
```
**Get the auth token**:
- Navigate to http://localhost:8000 (or https://localhost:8443)
- Log in with `admin@localhost` / `admin`
- Go to Settings > API Keys
- Create a new API key with read access
#### MCP Tool Usage
When using Bugsink MCP tools, remember:
- `mcp__bugsink__*` tools connect to **production/test** Bugsink
- `mcp__bugsink-dev__*` tools connect to **dev container** Bugsink
- Available capabilities for both:
- List projects and issues
- View detailed error events and stacktraces
- Search by error message or stack trace
- Update issue status (resolve, ignore)
- Create releases
### SSH Server Access

View File

@@ -147,6 +147,9 @@ ALLOWED_HOSTS = deduce_allowed_hosts(BUGSINK["BASE_URL"])\n\
\n\
# Console email backend for dev\n\
EMAIL_BACKEND = "bugsink.email_backends.QuietConsoleEmailBackend"\n\
\n\
# HTTPS proxy support (nginx reverse proxy on port 8443)\n\
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")\n\
' > /opt/bugsink/conf/bugsink_conf.py
# Create Bugsink startup script

View File

@@ -59,7 +59,11 @@ See [INSTALL.md](INSTALL.md) for detailed setup instructions.
## Environment Variables
This project uses environment variables for configuration (no `.env` files). Key variables:
**Production/Test**: Uses Gitea CI/CD secrets injected during deployment (no local `.env` files)
**Dev Container**: Uses `.env.local` file which **overrides** the default DSNs in `compose.dev.yml`
Key variables:
| Variable | Description |
| -------------------------------------------- | -------------------------------- |

View File

@@ -78,13 +78,16 @@ services:
- BUGSINK_DB_USER=bugsink
- BUGSINK_DB_PASSWORD=bugsink_dev_password
- BUGSINK_PORT=8000
- BUGSINK_BASE_URL=http://localhost:8000
- BUGSINK_BASE_URL=https://localhost:8443
- BUGSINK_ADMIN_EMAIL=admin@localhost
- BUGSINK_ADMIN_PASSWORD=admin
- BUGSINK_SECRET_KEY=dev-bugsink-secret-key-minimum-50-characters-for-security
# Sentry SDK configuration (points to local Bugsink HTTPS)
- SENTRY_DSN=https://cea01396-c562-46ad-b587-8fa5ee6b1d22@localhost:8443/1
- VITE_SENTRY_DSN=https://d92663cb-73cf-4145-b677-b84029e4b762@localhost:8443/2
# Sentry SDK configuration (points to local Bugsink HTTP)
# Note: Using HTTP with 127.0.0.1 instead of localhost because Sentry SDK
# doesn't accept 'localhost' as a valid hostname in DSN validation
# The browser accesses Bugsink at http://localhost:8000 (nginx proxies to HTTPS for the app)
- SENTRY_DSN=http://cea01396-c562-46ad-b587-8fa5ee6b1d22@127.0.0.1:8000/1
- VITE_SENTRY_DSN=http://d92663cb-73cf-4145-b677-b84029e4b762@127.0.0.1:8000/2
- SENTRY_ENVIRONMENT=development
- VITE_SENTRY_ENVIRONMENT=development
- SENTRY_ENABLED=true

View File

@@ -24,7 +24,29 @@ server {
# Allow large file uploads (matches production)
client_max_body_size 100M;
# Proxy all requests to Vite dev server on port 5173
# Proxy API requests to Express server on port 3001
location /api/ {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Proxy WebSocket connections for real-time notifications
location /ws {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Proxy all other requests to Vite dev server on port 5173
location / {
proxy_pass http://localhost:5173;
proxy_http_version 1.1;

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "flyer-crawler",
"version": "0.12.5",
"version": "0.12.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "flyer-crawler",
"version": "0.12.5",
"version": "0.12.6",
"dependencies": {
"@bull-board/api": "^6.14.2",
"@bull-board/express": "^6.14.2",

View File

@@ -1,7 +1,7 @@
{
"name": "flyer-crawler",
"private": true,
"version": "0.12.5",
"version": "0.12.6",
"type": "module",
"scripts": {
"dev": "concurrently \"npm:start:dev\" \"vite\"",

View File

@@ -52,9 +52,13 @@ NGINX_EOF
ln -sf /etc/nginx/sites-available/bugsink /etc/nginx/sites-enabled/bugsink
# Copy the dev nginx config from mounted volume to nginx sites-available
echo "📋 Copying nginx dev config..."
cp /app/docker/nginx/dev.conf /etc/nginx/sites-available/default
# Start nginx in background (if installed)
if command -v nginx &> /dev/null; then
echo "🌐 Starting nginx (HTTPS: Vite 5173 → 443, Bugsink 8000 → 8443)..."
echo "🌐 Starting nginx (HTTPS: Vite 5173 → 443, Bugsink 8000 → 8443, API 3001 → /api/)..."
nginx &
fi
@@ -62,6 +66,22 @@ fi
echo "📊 Starting Bugsink error tracking..."
/usr/local/bin/start-bugsink.sh > /var/log/bugsink/server.log 2>&1 &
# Wait for Bugsink to initialize, then run snappea migrations
echo "⏳ Waiting for Bugsink to initialize..."
sleep 5
echo "🔧 Running Bugsink snappea database migrations..."
cd /opt/bugsink/conf && \
export DATABASE_URL="postgresql://bugsink:bugsink_dev_password@postgres:5432/bugsink" && \
export SECRET_KEY="dev-bugsink-secret-key-minimum-50-characters-for-security" && \
/opt/bugsink/bin/bugsink-manage migrate --database=snappea > /dev/null 2>&1
# Start Snappea task worker
echo "🔄 Starting Snappea task worker..."
cd /opt/bugsink/conf && \
export DATABASE_URL="postgresql://bugsink:bugsink_dev_password@postgres:5432/bugsink" && \
export SECRET_KEY="dev-bugsink-secret-key-minimum-50-characters-for-security" && \
/opt/bugsink/bin/bugsink-manage runsnappea > /var/log/bugsink/snappea.log 2>&1 &
# Start Logstash in background
echo "📝 Starting Logstash..."
/usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/bugsink.conf > /var/log/logstash/logstash.log 2>&1 &

View File

@@ -15,7 +15,7 @@ export const Dashboard: React.FC = () => {
<RecipeSuggester />
{/* Other Dashboard Widgets */}
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/80">
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">Your Flyers</h2>
<FlyerCountDisplay />
</div>

View File

@@ -31,7 +31,7 @@ export const Header: React.FC<HeaderProps> = ({
// The state and handlers for the old AuthModal and SignUpModal have been removed.
return (
<>
<header className="bg-white dark:bg-gray-900 shadow-md sticky top-0 z-20">
<header className="bg-white dark:bg-gray-900 shadow-md sticky top-0 z-20 border-b-2 border-brand-primary dark:border-brand-secondary">
<div className="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<div className="flex items-center">

View File

@@ -43,7 +43,7 @@ export const Leaderboard: React.FC = () => {
}
return (
<div className="bg-white dark:bg-gray-800 shadow-lg rounded-lg p-6">
<div className="bg-white dark:bg-gray-800 shadow-lg rounded-lg p-6 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/80">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4 flex items-center">
<Award className="w-6 h-6 mr-2 text-blue-500" />
Top Users
@@ -57,7 +57,7 @@ export const Leaderboard: React.FC = () => {
{leaderboard.map((user) => (
<li
key={user.user_id}
className="flex items-center space-x-4 p-3 bg-gray-50 dark:bg-gray-700 rounded-lg transition hover:bg-gray-100 dark:hover:bg-gray-600"
className="flex items-center space-x-4 p-3 bg-gray-50 dark:bg-gray-700 rounded-lg transition-colors hover:bg-brand-light/30 dark:hover:bg-brand-dark/20"
>
<div className="shrink-0 w-8 text-center">{getRankIcon(user.rank)}</div>
<img

View File

@@ -48,7 +48,7 @@ export const RecipeSuggester: React.FC = () => {
);
return (
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/80">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
Get a Recipe Suggestion
</h2>

View File

@@ -155,9 +155,15 @@ export const MainLayout: React.FC<MainLayoutProps> = ({
unitSystem={'imperial'} // This can be passed down or sourced from a context
user={user}
/>
<PriceHistoryChart />
<Leaderboard />
<ActivityLog userProfile={userProfile} onLogClick={handleActivityLogClick} />
{user && (
<>
<PriceHistoryChart />
<Leaderboard />
{userProfile && (
<ActivityLog userProfile={userProfile} onLogClick={handleActivityLogClick} />
)}
</>
)}
</>
</div>
</div>

View File

@@ -38,27 +38,27 @@ describe('Admin Route Authorization', () => {
const adminEndpoints = [
{
method: 'GET',
path: '/admin/stats',
path: '/api/admin/stats',
},
{
method: 'GET',
path: '/admin/users',
path: '/api/admin/users',
},
{
method: 'GET',
path: '/admin/corrections',
path: '/api/admin/corrections',
},
{
method: 'POST',
path: '/admin/corrections/1/approve',
path: '/api/admin/corrections/1/approve',
},
{
method: 'POST',
path: '/admin/trigger/daily-deal-check',
path: '/api/admin/trigger/daily-deal-check',
},
{
method: 'GET',
path: '/admin/queues/status',
path: '/api/admin/queues/status',
},
];

View File

@@ -62,7 +62,7 @@ describe('E2E Admin Dashboard Flow', () => {
// 4. Fetch System Stats (Protected Admin Route)
const statsResponse = await getRequest()
.get('/admin/stats')
.get('/api/admin/stats')
.set('Authorization', `Bearer ${authToken}`);
expect(statsResponse.status).toBe(200);
@@ -71,7 +71,7 @@ describe('E2E Admin Dashboard Flow', () => {
// 5. Fetch User List (Protected Admin Route)
const usersResponse = await getRequest()
.get('/admin/users')
.get('/api/admin/users')
.set('Authorization', `Bearer ${authToken}`);
expect(usersResponse.status).toBe(200);
@@ -84,7 +84,7 @@ describe('E2E Admin Dashboard Flow', () => {
// 6. Check Queue Status (Protected Admin Route)
const queueResponse = await getRequest()
.get('/admin/queues/status')
.get('/api/admin/queues/status')
.set('Authorization', `Bearer ${authToken}`);
expect(queueResponse.status).toBe(200);

View File

@@ -310,7 +310,7 @@ describe('E2E Budget Management Journey', () => {
// Step 15: Delete account
const deleteAccountResponse = await getRequest()
.delete('/api/user/account')
.delete('/api/users/account')
.set('Authorization', `Bearer ${authToken}`)
.send({ password: userPassword });

View File

@@ -373,7 +373,7 @@ describe('E2E Deals and Price Tracking Journey', () => {
// Step 11: Delete account
const deleteAccountResponse = await getRequest()
.delete('/api/user/account')
.delete('/api/users/account')
.set('Authorization', `Bearer ${authToken}`)
.send({ password: userPassword });

View File

@@ -408,7 +408,7 @@ describe('E2E Inventory/Expiry Management Journey', () => {
// Step 20: Delete account
const deleteAccountResponse = await getRequest()
.delete('/api/user/account')
.delete('/api/users/account')
.set('Authorization', `Bearer ${authToken}`)
.send({ password: userPassword });

View File

@@ -318,7 +318,7 @@ describe('E2E Receipt Processing Journey', () => {
// Step 19: Delete account
const deleteAccountResponse = await getRequest()
.delete('/api/user/account')
.delete('/api/users/account')
.set('Authorization', `Bearer ${authToken}`)
.send({ password: userPassword });

View File

@@ -219,7 +219,7 @@ describe('E2E UPC Scanning Journey', () => {
// Step 12: Delete account (self-service)
const deleteAccountResponse = await getRequest()
.delete('/api/user/account')
.delete('/api/users/account')
.set('Authorization', `Bearer ${authToken}`)
.send({ password: userPassword });