Files
byrachel/README.md
T
2026-07-24 11:34:00 -03:00

560 lines
19 KiB
Markdown

# ByRachel — Clothing Business Management
A full-featured web application for managing a small clothing business. Built with Next.js, optimized for deployment on a single VPS with Docker.
## Features
- **Product Catalog** — Products with variants (size/color), images, traffic light inventory system
- **Sales Management** — Full sales workflow: create, confirm, deliver, cancel, returns
- **Purchase Orders** — Supplier purchases with receive/confirm/cancel workflow
- **Inventory** — Movement-based stock tracking, restock suggestions, adjustments
- **Customers** — Customer management with tags and sale history
- **Suppliers** — Supplier management with product associations
- **Categories** — Hierarchical categories with color coding
- **Analytics** — Sales trends, revenue/COGS/profit KPIs, best sellers, seasonality, cost evolution, inventory health
- **Reports** — PDF generation for sales, purchases, inventory, customers, brand, analytics
- **Public Catalog** — Customer-facing product catalog at `/catalogo`
- **Settings** — Brand configuration, pricing rules, password management, TOTP 2FA
- **Audit Logging** — All critical operations are logged with before/after data
## Tech Stack
| Layer | Technology |
|-------|-----------|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript (strict mode) |
| Styling | Tailwind CSS 4 |
| UI Components | shadcn/ui (Radix UI primitives) |
| Database | SQLite (better-sqlite3) |
| ORM | Drizzle ORM |
| Authentication | JWT (jose) + TOTP (otplib) + Argon2 password hashing |
| Charts | Recharts |
| PDF Generation | pdf-lib (client-side) |
| Image Processing | Sharp |
| Testing | Vitest |
| Drag & Drop | @dnd-kit |
| Validation | Zod |
| Container | Docker (multi-stage build) |
## Prerequisites
- **Node.js 22+** and npm (for development)
- **Docker & Docker Compose** (for container deployment)
- **A domain name** (for production HTTPS, optional for local use)
## Quick Start (Docker)
The fastest way to get ByRachel running:
```bash
# 1. Clone the repository
git clone <your-repo-url> byrachel
cd byrachel
# 2. Create environment file
cp .env.example .env
# Edit .env and change JWT_SECRET to a random secure value:
# openssl rand -base64 32
# 3. Start the application
docker compose up -d
# 4. Open in browser
# http://localhost:3000
# You'll be redirected to /setup to create your admin account
```
## Development Setup
```bash
# 1. Clone and install
git clone <your-repo-url> byrachel
cd byrachel
npm install
# 2. Set up environment
cp .env.example .env
# 3. Initialize the database
npm run db:push
# 4. (Optional) Seed sample data
npm run seed
# 5. Start development server
npm run dev
# Open http://localhost:3000
```
### Development Commands
| Command | Description |
|---------|-------------|
| `npm run dev` | Start development server with hot reload |
| `npm run build` | Create production build |
| `npm run start` | Start production server |
| `npm run test` | Run all tests |
| `npm run test:watch` | Run tests in watch mode |
| `npm run test:coverage` | Run tests with coverage report |
| `npm run db:push` | Push schema changes to database |
| `npm run db:generate` | Generate migration files |
| `npm run db:migrate` | Run pending migrations |
| `npm run db:studio` | Open Drizzle Studio (database GUI) |
| `npm run seed` | Seed database with sample data |
## Environment Variables
| Variable | Description | Default | Required |
|----------|-------------|---------|----------|
| `DATABASE_URL` | SQLite database file path. Dev: `./data/db.sqlite`, Docker: `/app/data/db.sqlite` | `./data/db.sqlite` | Yes |
| `JWT_SECRET` | Secret key for JWT signing. **MUST be changed in production.** Generate with: `openssl rand -base64 32` | `change-me-to-a-256-bit-secret` | Yes |
| `UPLOAD_DIR` | Directory for uploaded images. Dev: `./public/uploads`, Docker: `/app/public/uploads` | `./public/uploads` | Yes |
| `STORAGE_ADAPTER` | Storage backend. Currently only `local` (filesystem) is supported | `local` | Yes |
| `NODE_ENV` | Node environment: `development` or `production` | `production` | Yes |
| `NEXT_TELEMETRY_DISABLED` | Disable Next.js telemetry. Set to `1` to disable | `1` | No |
## API Routes Reference
All API routes are under `/api/`. Admin routes require a valid JWT cookie (except `/api/health` and `/api/auth/*`).
### Authentication
| Method | Route | Description |
|--------|-------|-------------|
| POST | `/api/auth/setup` | Initial admin account creation (first run only) |
| POST | `/api/auth/login` | Login with email/password |
| POST | `/api/auth/verify-totp` | Verify TOTP code |
| GET | `/api/auth/me` | Get current user info |
| POST | `/api/auth/logout` | Logout (clear session) |
| PUT | `/api/auth/change-password` | Change password |
| POST | `/api/auth/regenerate-recovery-codes` | Generate new recovery codes |
### Products
| Method | Route | Description |
|--------|-------|-------------|
| GET/POST | `/api/products` | List / Create products |
| GET/PUT/DELETE | `/api/products/[id]` | Get / Update / Delete product |
| POST | `/api/products/[id]/duplicate` | Duplicate a product |
| GET/POST | `/api/products/[id]/images` | List / Upload product images |
| DELETE | `/api/products/[id]/images/[imageId]` | Delete product image |
| PUT | `/api/products/[id]/images/reorder` | Reorder product images |
| GET/POST | `/api/products/[id]/variants` | List / Create variants |
| PUT/DELETE | `/api/products/[id]/variants/[variantId]` | Update / Delete variant |
| GET | `/api/products/[id]/variants/[variantId]/movements` | Get variant movements |
### Categories
| Method | Route | Description |
|--------|-------|-------------|
| GET/POST | `/api/categories` | List / Create categories |
| PUT/DELETE | `/api/categories/[id]` | Update / Delete category |
### Sales
| Method | Route | Description |
|--------|-------|-------------|
| GET/POST | `/api/sales` | List / Create sales |
| GET/PUT/DELETE | `/api/sales/[id]` | Get / Update / Delete sale |
| POST | `/api/sales/[id]/confirm` | Confirm sale |
| POST | `/api/sales/[id]/deliver` | Mark sale as delivered |
| POST | `/api/sales/[id]/cancel` | Cancel sale |
| POST | `/api/sales/[id]/return` | Process sale return |
### Purchases
| Method | Route | Description |
|--------|-------|-------------|
| GET/POST | `/api/purchases` | List / Create purchases |
| GET/PUT/DELETE | `/api/purchases/[id]` | Get / Update / Delete purchase |
| POST | `/api/purchases/[id]/receive` | Receive purchase items |
| POST | `/api/purchases/[id]/confirm` | Confirm purchase |
| POST | `/api/purchases/[id]/cancel` | Cancel purchase |
### Inventory
| Method | Route | Description |
|--------|-------|-------------|
| GET | `/api/inventory` | Get inventory status |
| POST | `/api/inventory/restock` | Restock variants |
| POST | `/api/inventory/adjust` | Manual stock adjustment |
| GET | `/api/inventory/movements` | List stock movements |
### Customers
| Method | Route | Description |
|--------|-------|-------------|
| GET/POST | `/api/customers` | List / Create customers |
| GET/PUT/DELETE | `/api/customers/[id]` | Get / Update / Delete customer |
| GET/POST | `/api/customers/[id]/tags` | List / Add customer tags |
| DELETE | `/api/customers/[id]/tags/[tagId]` | Remove customer tag |
| GET | `/api/customer-tags` | List all customer tags |
### Suppliers
| Method | Route | Description |
|--------|-------|-------------|
| GET/POST | `/api/suppliers` | List / Create suppliers |
| GET/PUT/DELETE | `/api/suppliers/[id]` | Get / Update / Delete supplier |
| GET | `/api/suppliers/[id]/products` | Get supplier's products |
### Analytics
| Method | Route | Description |
|--------|-------|-------------|
| GET | `/api/analytics/summary` | Sales summary KPIs |
| GET | `/api/analytics/sales-trend` | Revenue/units over time |
| GET | `/api/analytics/categories` | Sales by category |
| GET | `/api/analytics/products` | Product performance |
| GET | `/api/analytics/customers` | Customer analytics |
| GET | `/api/analytics/cost-evolution` | Cost price evolution |
| GET | `/api/analytics/inventory-health` | Inventory health metrics |
| GET | `/api/analytics/seasonality` | Seasonal sales patterns |
### Reports (PDF Generation)
| Method | Route | Description |
|--------|-------|-------------|
| GET | `/api/reports/sales` | Sales report data |
| GET | `/api/reports/sales/[id]` | Single sale report |
| GET | `/api/reports/sales/profits` | Profit report |
| GET | `/api/reports/purchases` | Purchases report data |
| GET | `/api/reports/purchases/[id]` | Single purchase report |
| GET | `/api/reports/inventory` | Inventory report |
| GET | `/api/reports/customers` | Customers report |
| GET | `/api/reports/customers/[id]` | Single customer report |
| GET | `/api/reports/brand` | Brand/catalog report |
| GET | `/api/reports/analytics/best-sellers` | Best sellers report |
| GET | `/api/reports/analytics/low-rotation` | Low rotation report |
### Settings
| Method | Route | Description |
|--------|-------|-------------|
| GET/PUT | `/api/settings/brand` | Get / Update brand settings |
| GET/PUT | `/api/settings/pricing` | Get / Update pricing settings |
### Admin
| Method | Route | Description |
|--------|-------|-------------|
| GET | `/api/admin/backup` | Download full backup (tar.gz) |
### Other
| Method | Route | Description |
|--------|-------|-------------|
| GET | `/api/health` | Health check (public, no auth) |
| POST | `/api/upload` | Upload image file |
## Backup & Restore
### Automated Backup (Admin Endpoint)
Admin users can download a full backup from the app:
```
GET /api/admin/backup
```
This returns a `tar.gz` archive containing:
- `data/` — SQLite database files (main DB, WAL, SHM)
- `public/uploads/` — All uploaded images
**Requirements**: Must be authenticated as admin. Download over HTTPS only.
### Manual SQLite Backup
SQLite with WAL mode requires a safe backup procedure to avoid corruption:
```bash
# Option 1: Using SQLite .backup command (safest)
docker compose exec app sh -c 'sqlite3 /app/data/db.sqlite ".backup /app/data/backup.db"'
# Then copy backup.db from the container
# Option 2: Copy WAL-safe (stop writes first)
# 1. Temporarily stop the application
docker compose stop app
# 2. Copy the database files
docker compose cp app:/app/data/db.sqlite ./backup/db.sqlite
docker compose cp app:/app/data/db.sqlite-wal ./backup/db.sqlite-wal
docker compose cp app:/app/data/db.sqlite-shm ./backup/db.sqlite-shm
# 3. Restart the application
docker compose start app
# Option 3: Using docker volume backup
docker run --rm -v byrachel_db-data:/data -v $(pwd)/backup:/backup alpine \
tar czf /backup/byrachel-db-$(date +%Y%m%d).tar.gz -C /data .
```
### Image Volume Backup
```bash
docker run --rm -v byrachel_uploads:/uploads -v $(pwd)/backup:/backup alpine \
tar czf /backup/byrachel-uploads-$(date +%Y%m%d).tar.gz -C /uploads .
```
### Restore Procedure
```bash
# 1. Stop the application
docker compose stop app
# 2. Restore database
docker run --rm -v byrachel_db-data:/data -v $(pwd)/backup:/backup alpine \
sh -c 'rm -rf /data/* && tar xzf /backup/byrachel-db-YYYYMMDD.tar.gz -C /data'
# 3. Restore uploads
docker run --rm -v byrachel_uploads:/uploads -v $(pwd)/backup:/backup alpine \
sh -c 'rm -rf /uploads/* && tar xzf /backup/byrachel-uploads-YYYYMMDD.tar.gz -C /uploads'
# 4. Start the application
docker compose start app
# 5. Verify
docker compose ps # Check health status
curl http://localhost:3000/api/health # Should return {"status":"ok"}
```
## Production Deployment
### Docker Deployment
```bash
# 1. Clone on your server
git clone <your-repo-url> byrachel
cd byrachel
# 2. Configure environment
cp .env.example .env
nano .env
# Set JWT_SECRET to a random value: openssl rand -base64 32
# 3. Build and start
docker compose up -d --build
# 4. Verify
docker compose ps
docker compose logs -f app
```
### Nginx Proxy Manager Setup
[Nginx Proxy Manager](https://nginxproxymanager.com/) is the recommended reverse proxy for HTTPS and domain routing.
#### 1. Install Nginx Proxy Manager
```bash
# Add to your docker-compose.yml or run separately
docker run -d \
--name nginx-proxy-manager \
-p 80:80 -p 81:81 -p 443:443 \
-v ./npm-data:/data \
-v ./npm-le:/etc/letsencrypt \
jc21/nginx-proxy-manager:latest
```
#### 2. Configure Proxy Host
1. Open NPM admin: `http://your-server-ip:81`
2. Default login: `admin@example.com` / `changeme`
3. Go to **Hosts → Proxy Hosts → Add Proxy Host**
4. Configure:
- **Domain Names**: `yourdomain.com`
- **Scheme**: `http`
- **Hostname**: `byrachel-app` (Docker container name) or your server's Docker IP
- **Port**: `3000`
- **Common Settings**:
- Enable: `Block Common Exploits`
- Enable: `Websockets Support`
- **SSL Tab**:
- SSL Certificate: Request a new Let's Encrypt certificate
- Enable: `Force SSL`
- Enable: `HTTP/2 Support`
#### 3. Docker Network (recommended)
If both NPM and ByRachel are in the same Docker network:
```yaml
# docker-compose.yml
services:
app:
# ... existing config ...
networks:
- proxy-net
networks:
proxy-net:
external: true
```
Then in NPM, use the container name `byrachel-app` as the hostname.
### HTTPS Configuration
HTTPS is configured through Nginx Proxy Manager:
1. **Request Certificate**: In the Proxy Host SSL tab, select "Request a new SSL Certificate"
2. **Let's Encrypt**: NPM handles Let's Encrypt automatically
3. **Force SSL**: Enable to redirect HTTP → HTTPS
4. **Auto-renewal**: Certificates renew automatically
### Domain Configuration
1. **DNS Record**: Create an A record pointing your domain to your server's IP
```
Type: A
Name: yourdomain.com (or @)
Value: YOUR_SERVER_IP
TTL: 300
```
2. **Wait for DNS propagation** (usually minutes, up to 48h)
3. **Configure in NPM**: Add the domain in the Proxy Host settings (see above)
### Auto-restart & Log Management
#### Auto-restart
The Docker Compose configuration includes `restart: unless-stopped`, which means:
- Container restarts automatically on crash
- Container starts on Docker daemon startup (server reboot)
- Container only stays stopped if you explicitly `docker compose stop`
For additional server-level auto-start, create a systemd service:
```ini
# /etc/systemd/system/byrachel.service
[Unit]
Description=ByRachel Docker Compose
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/path/to/byrachel
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl enable byrachel.service
sudo systemctl start byrachel.service
```
#### Log Management
```bash
# View live logs
docker compose logs -f app
# View last 100 lines
docker compose logs --tail=100 app
# Configure log rotation (in docker-compose.yml, add to app service):
# logging:
# driver: json-file
# options:
# max-size: "10m"
# max-file: "3"
```
## SQLite → PostgreSQL Migration Plan
ByRachel uses Drizzle ORM, which supports multiple database dialects. Migration to PostgreSQL is possible when needed:
### Steps
1. **Install PostgreSQL driver**:
```bash
npm install pg
npm install @types/pg
```
2. **Update Drizzle config** (`drizzle.config.ts`):
```typescript
export default defineConfig({
schema: './src/lib/db/schema.ts',
out: './src/lib/db/migrations',
dialect: 'postgresql', // Change from 'sqlite'
dbCredentials: {
host: process.env.PGHOST,
port: Number(process.env.PGPORT),
user: process.env.PGUSER,
password: process.env.PGPASSWORD,
database: process.env.PGDATABASE,
},
});
```
3. **Update database connection** (`src/lib/db/index.ts`):
- Replace `drizzle(betterSqlite3(...))` with `drizzle(pgPool)` or `drizzle(nodePostgres(...))`
4. **Update environment variables**:
```env
DATABASE_URL=postgresql://user:password@localhost:5432/byrachel
```
5. **Generate and run migrations**:
```bash
npm run db:generate
npm run db:migrate
```
6. **Data migration**: Export data from SQLite and import into PostgreSQL using a migration script.
### Considerations
- SQLite uses `integer` primary keys with auto-increment — PostgreSQL uses `serial` or `identity`
- SQLite `boolean` (integer 0/1) maps directly to PostgreSQL `boolean`
- SQLite `text` timestamps work in both, but PostgreSQL has native `timestamp` types
- Test thoroughly in a staging environment before migrating production data
## First-Run Setup
### 1. Initial Admin Account
1. Open the app in your browser (e.g., `http://localhost:3000`)
2. You'll be redirected to `/setup` (only available when no users exist)
3. Fill in:
- **Email**: Admin email address
- **Password**: Strong password (min 8 characters)
- **Name**: Admin display name (optional)
4. Click "Create Account"
### 2. TOTP Configuration
After creating the account, TOTP (Time-based One-Time Password) setup begins automatically:
1. A **QR code** is displayed on screen
2. Open your authenticator app (Google Authenticator, Authy, 1Password, etc.)
3. Scan the QR code (or enter the secret key manually)
4. Enter the 6-digit code from your authenticator app to verify
5. **Save your recovery codes** in a secure location — these are your backup if you lose access to your authenticator
### 3. Brand Configuration
After logging in for the first time:
1. Navigate to **Settings** (gear icon in sidebar)
2. Configure your brand:
- **Store Name**: Your business name
- **Logo**: Upload your logo image
- **Tagline**: Short description
- **Colors**: Primary and secondary brand colors
- **Contact Info**: Phone, email, WhatsApp
- **Address**: Business address
- **Footer Text**: Custom footer for catalog pages
3. Click "Save Settings"
4. To publish your public catalog, check "Publish Catalog" in settings
## Known Limitations
- **SQLite write concurrency**: SQLite handles concurrent reads well but writes are serialized. For a single-admin small business, this is not an issue. If you need multi-writer support, migrate to PostgreSQL (see migration plan above).
- **No built-in email**: The app does not send emails. Password recovery uses local recovery codes instead of email-based reset.
- **Single admin**: The app supports admin and operator roles, but the initial setup creates only one admin user. Additional users must be created manually via the database.
- **No automated backups**: The backup endpoint provides on-demand backups. Set up cron jobs or external tools for scheduled backups.
- **Local file storage only**: Images are stored on the local filesystem. For multi-instance deployments, a shared storage solution (S3, etc.) would be needed.
- **No real-time updates**: The app uses standard HTTP requests. Changes made in one browser tab are not reflected in other tabs until refresh.
- **Catalog is read-only for customers**: The public catalog at `/catalogo` is for browsing only. Customers cannot place orders through the app.
## License
Private — All rights reserved.