first commit
This commit is contained in:
@@ -0,0 +1,559 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user