Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ packages/shared/ zod schemas + TS DTOs mirroring DB (canonical client contract
docs/ ARCHITECTURE.md, ERD.md, DFD.md, planning/
tools/ composer.phar (vendored, no global composer)
DECISIONS.md locked stack + flows (source of truth)
compose.yaml optional Docker Desktop local-dev stack (not production)
```

Three client mirrors must stay in sync on any schema/enum change: `db/schema.sql`, `packages/shared/src/index.ts`, `apps/web/src/types.ts`.
Expand Down Expand Up @@ -58,6 +59,9 @@ bunx cap open android # build APK in Android Studio
# Root shortcuts
bun run dev # runs web dev server
bun run build:mobile # web build + cap sync

# Optional Docker local stack (repo root)
docker compose up --build # web :5173, api :8000, mailpit :8025, mysql :3307
```

## 2a. Known-good production deployment contract
Expand Down Expand Up @@ -88,6 +92,9 @@ free-tier deployment contract:
`http://10.0.2.2` for emulator / compatibility APK testing
- Mobile: `bun run build:mobile` builds against the deployed Render API;
`bun run build:mobile:local` is only for local emulator API testing
- Local Docker: `compose.yaml` is optional dev tooling only. It may use
MySQL/Mailpit/Tesseract locally, but it does not replace Render, Vercel,
TiDB, or Android Studio production/test responsibilities.

Never store TiDB, Cloudinary, Brevo, APP_KEY, or SMTP secrets in docs, memory,
or committed files.
Expand Down
6 changes: 6 additions & 0 deletions DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
| Image storage | Cloudinary | Payment proofs + menu photos; signed uploads from PHP |
| Auth | PHP sessions, `password_hash`, `session_regenerate_id`, idle timeout, role in session | Laravel session driver + CSRF |
| Hosting | API: Render free Docker web service; DB: TiDB Cloud Starter; SPA: Vercel static app; Mobile: Capacitor APK | XAMPP for local dev; provider subdomains are the free domains |
| Optional local dev | Docker Compose | Local API + MySQL + Mailpit + Tesseract + Vite parity stack; does not replace production providers |

## 2. Why this stack (rubric alignment)

Expand Down Expand Up @@ -220,6 +221,11 @@ Statuses (ENUM): `pending`, `payment_uploaded`, `confirmed`, `preparing`, `ready
- Because Vercel and Render are different sites, production session cookies
default to `SameSite=None; Secure`. Local development stays `SameSite=Lax`
unless `.env` overrides it.
- Docker Compose is allowed as an optional local parity layer. It exists to
make Laravel/MySQL/Tesseract/Mailpit development reproducible on machines
with Docker Desktop. It must not replace the known-good production contract:
Vercel for web, Render for API, TiDB Cloud for production data, and direct
Render API targeting for production APK builds.

### 8j. Customer account completion and pickup-aware UX
- Payment proofs are digital receipts, so customer receipt upload must open the
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<a href="https://thaijoint-prepflow.vercel.app/admin">Admin Dashboard</a> |
<a href="https://prepflow-api.onrender.com/api/menu">API Health</a> |
<a href="docs/deployment/free-tier.md">Deployment Guide</a> |
<a href="docs/development/docker-local.md">Docker Local Dev</a> |
<a href="docs/planning/session-final-report.md">Final Report</a> |
<a href="docs/planning/deployment-hardening-session-2026-06-28.md">Ops Session Log</a>
</p>
Expand Down Expand Up @@ -71,6 +72,7 @@ Use these dashboards when something breaks in production. Do not put secrets in
| Cloudinary | PrepFlow image cloud | Menu photos, payment proofs, payment-method QR images, OCR input URLs | API stores Cloudinary URLs only; no data URLs in DB |
| Brevo | Transactional email | SMTP credentials, sender verification, delivery/open logs | Render uses SMTP on port `2525`; domain auth still needs an owned domain |
| Android Studio | Local `apps/mobile/android` project | Debug APK build/install/logcat | `bun run build:mobile` targets deployed Render API for production APK testing |
| Docker Desktop | Local `compose.yaml` | Optional repeatable dev stack for API, MySQL, Mailpit, Vite, Tesseract | Use for local parity; not a replacement for Vercel/Render/TiDB dashboards |

Provider dashboard details and recovery steps live in [docs/deployment/free-tier.md](docs/deployment/free-tier.md). The June 28 deployment/auth hardening log is in [docs/planning/deployment-hardening-session-2026-06-28.md](docs/planning/deployment-hardening-session-2026-06-28.md).

Expand Down Expand Up @@ -198,15 +200,40 @@ PrepFlow/

## Local Development

Two local paths are supported:

- **XAMPP path:** closest to the original classroom setup and still fully
supported.
- **Docker path:** optional, repeatable local stack for Laravel + MySQL +
Mailpit + Tesseract + Vite. This is useful for avoiding XAMPP drift and
matching Render's Linux/Tesseract runtime more closely.

### Prerequisites

- Windows + PowerShell
- XAMPP with PHP 8.2 and MySQL/MariaDB (`C:\xampp\php` expected)
- Bun 1.3+
- Docker Desktop, optional but recommended for backend/OCR/email parity
- Android Studio + Android SDK for APK work
- Cloudinary account for image upload testing
- Optional local OCR: Tesseract at `C:\Program Files\Tesseract-OCR\tesseract.exe`

### Docker Quick Start

```powershell
cd D:\OneDrive\Desktop\Final-Project\PrepFlow
docker compose up --build
```

Open:

- Web: http://localhost:5173
- API health: http://localhost:8000/api/menu
- Mailpit email inbox: http://localhost:8025

Docker uses local dev credentials only and runs migrations + seeders on start.
Full details: [docs/development/docker-local.md](docs/development/docker-local.md).

### 1. Install Workspace Dependencies

```powershell
Expand Down Expand Up @@ -387,6 +414,7 @@ Read [AGENTS.md](AGENTS.md) and [DECISIONS.md](DECISIONS.md) before editing impl
| [docs/ERD.md](docs/ERD.md) | Entity relationship model |
| [docs/DFD.md](docs/DFD.md) | Data flow diagrams |
| [docs/deployment/free-tier.md](docs/deployment/free-tier.md) | Render + TiDB + Vercel deployment guide |
| [docs/development/docker-local.md](docs/development/docker-local.md) | Optional Docker Compose local development guide |
| [docs/planning/session-final-report.md](docs/planning/session-final-report.md) | Implementation and verification report |
| [docs/planning/deployment-hardening-session-2026-06-28.md](docs/planning/deployment-hardening-session-2026-06-28.md) | Deployment/auth/email/OCR/APK hardening session log |
| [docs/planning/completion-audit.md](docs/planning/completion-audit.md) | Historical audit baseline |
Expand Down
35 changes: 35 additions & 0 deletions api/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
FROM php:8.2-apache

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
curl \
default-mysql-client \
git \
libsqlite3-dev \
tesseract-ocr \
unzip \
zip \
&& docker-php-ext-install pdo_mysql pdo_sqlite \
&& a2enmod headers rewrite \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /var/www/html

COPY tools/composer.phar /tmp/composer.phar
COPY api/ ./
COPY api/docker/apache.conf /etc/apache2/sites-available/000-default.conf
COPY api/docker/dev-start.sh /usr/local/bin/prepflow-dev-start
COPY api/docker/test.sh /usr/local/bin/prepflow-test

RUN mkdir -p bootstrap/cache storage/logs storage/framework/cache storage/framework/sessions storage/framework/views \
&& chmod -R ug+rwx bootstrap/cache storage \
&& chmod +x /usr/local/bin/prepflow-dev-start /usr/local/bin/prepflow-test \
&& chown -R www-data:www-data storage bootstrap/cache

ENV OCR_TECHNIQUE=tesseract
ENV TESSERACT_BIN=/usr/bin/tesseract

EXPOSE 80

CMD ["prepflow-dev-start"]
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
{
public function up(): void
{
DB::statement('DROP VIEW IF EXISTS v_daily_procurement');

DB::statement(
"CREATE VIEW v_daily_procurement AS
SELECT ri.ingredient_name,
Expand Down
37 changes: 37 additions & 0 deletions api/docker/dev-start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail

mkdir -p storage/logs storage/framework/cache storage/framework/sessions storage/framework/views bootstrap/cache
chmod -R ug+rwx storage bootstrap/cache

if [ -z "${APP_KEY:-}" ]; then
export APP_KEY
APP_KEY="$(php -r 'echo "base64:".base64_encode(random_bytes(32));')"
fi

if [ ! -f vendor/autoload.php ]; then
php /tmp/composer.phar install --no-interaction --prefer-dist || php /tmp/composer.phar install --no-interaction --prefer-source
fi

if [ "${DB_CONNECTION:-}" = "mysql" ]; then
until MYSQL_PWD="${DB_PASSWORD:-}" mysqladmin ping -h"${DB_HOST:-mysql}" -P"${DB_PORT:-3306}" -u"${DB_USERNAME:-root}" --silent; do
echo "Waiting for MySQL..."
sleep 2
done
fi

if [ "${RUN_MIGRATIONS:-false}" = "true" ]; then
php artisan migrate --force
fi

if [ "${RUN_SEEDERS:-false}" = "true" ]; then
php artisan db:seed --force
fi

php artisan config:clear --no-interaction
php artisan route:clear --no-interaction
php artisan view:clear --no-interaction

chown -R www-data:www-data storage bootstrap/cache

exec apache2-foreground
18 changes: 18 additions & 0 deletions api/docker/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail

APP_ENV=testing \
APP_MAINTENANCE_DRIVER=file \
BCRYPT_ROUNDS=4 \
BROADCAST_CONNECTION=null \
CACHE_STORE=array \
DB_CONNECTION=sqlite \
DB_DATABASE=':memory:' \
DB_URL='' \
MAIL_MAILER=array \
QUEUE_CONNECTION=sync \
SESSION_DRIVER=array \
PULSE_ENABLED=false \
TELESCOPE_ENABLED=false \
NIGHTWATCH_ENABLED=false \
php artisan test "$@"
2 changes: 1 addition & 1 deletion apps/web/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default defineConfig({
server: {
port: 5173,
proxy: {
'/api': 'http://127.0.0.1:8000',
'/api': process.env.VITE_DEV_API_PROXY || 'http://127.0.0.1:8000',
},
},
build: {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default defineConfig({
server: {
port: 5173,
proxy: {
'/api': 'http://127.0.0.1:8000',
'/api': process.env.VITE_DEV_API_PROXY || 'http://127.0.0.1:8000',
},
},
build: {
Expand Down
113 changes: 113 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: prepflow-dev

services:
mysql:
image: mysql:8.4
environment:
MYSQL_DATABASE: prepflow
MYSQL_USER: prepflow
MYSQL_PASSWORD: prepflow
MYSQL_ROOT_PASSWORD: prepflow_root
ports:
- "3307:3306"
volumes:
- prepflow_mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uprepflow -pprepflow --silent"]
interval: 5s
timeout: 3s
retries: 20
start_period: 30s

mailpit:
image: axllent/mailpit:v1.20
ports:
- "1025:1025"
- "8025:8025"

api:
build:
context: .
dockerfile: api/Dockerfile.dev
depends_on:
mysql:
condition: service_healthy
environment:
APP_NAME: PrepFlow
APP_ENV: local
APP_DEBUG: "true"
APP_URL: http://localhost:8000
FRONTEND_URL: http://localhost:5173
DB_CONNECTION: mysql
DB_HOST: mysql
DB_PORT: "3306"
DB_DATABASE: prepflow
DB_USERNAME: prepflow
DB_PASSWORD: prepflow
SESSION_DRIVER: database
SESSION_DOMAIN: "null"
SESSION_SAME_SITE: lax
SESSION_SECURE_COOKIE: "false"
CACHE_STORE: database
QUEUE_CONNECTION: database
MAIL_MAILER: smtp
MAIL_HOST: mailpit
MAIL_PORT: "1025"
MAIL_USERNAME: "null"
MAIL_PASSWORD: "null"
MAIL_FROM_ADDRESS: hello@prepflow.local
MAIL_FROM_NAME: PrepFlow
CORS_ALLOWED_ORIGINS: http://localhost,http://localhost:5173,http://127.0.0.1:5173,http://10.0.2.2,https://localhost
CLOUDINARY_URL: ${CLOUDINARY_URL:-}
OCR_TECHNIQUE: tesseract
TESSERACT_BIN: /usr/bin/tesseract
RUN_MIGRATIONS: "true"
RUN_SEEDERS: "true"
ports:
- "8000:80"
volumes:
- ./api:/var/www/html
- prepflow_api_storage:/var/www/html/storage
- prepflow_api_cache:/var/www/html/bootstrap/cache
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost/api/menu >/dev/null || exit 1"]
interval: 10s
timeout: 10s
retries: 30
start_period: 240s

web:
image: oven/bun:1.3
depends_on:
api:
condition: service_healthy
working_dir: /workspace
environment:
VITE_API_URL: /api
VITE_DEV_API_PROXY: http://api
command: sh -lc "bun install --frozen-lockfile && bun run --cwd apps/web dev --host 0.0.0.0"
ports:
- "5173:5173"
volumes:
- .:/workspace
- prepflow_node_modules:/workspace/node_modules
- prepflow_bun_cache:/root/.bun/install/cache

adminer:
image: adminer:4
profiles:
- tools
depends_on:
mysql:
condition: service_healthy
environment:
ADMINER_DEFAULT_SERVER: mysql
ports:
- "8081:8080"

volumes:
prepflow_mysql_data:
prepflow_api_storage:
prepflow_api_cache:
prepflow_node_modules:
prepflow_bun_cache:
Loading