# Complete Setup & Build Guide This document covers everything needed to develop, build, and deploy Craft2Prints. ## Table of Contents 1. [Local Development Setup](#local-development-setup) 2. [Configuration Reference](#configuration-reference) 3. [Database Management](#database-management) 4. [Image & File Storage](#image--file-storage) 5. [Feature Setup](#feature-setup) 6. [Building for Production](#building-for-production) 7. [Deployment](#deployment) 8. [Troubleshooting](#troubleshooting) --- ## Local Development Setup ### Prerequisites **Node.js 18 or later** is required: ```bash node -v # Check your version ``` If not installed, download from https://nodejs.org (LTS version recommended). **PostgreSQL** — the app connects to a Postgres database (`prisma/schema.prisma`'s `datasource` is fixed to `postgresql`). Install it locally (https://www.postgresql.org/download/) or point `DATABASE_URL` at any reachable Postgres instance (a Docker container, a managed database, another machine on your network). ### Step 1: Clone and Install ```bash # Clone the repository git clone cd craft2prints # Install dependencies npm install # This automatically runs: npx prisma generate ``` ### Step 2: Environment Setup ```bash # Copy example environment file cp .env.example .env ``` Edit `.env` and configure at minimum: ```env DATABASE_URL="postgresql://user:password@localhost:5432/craft2prints?schema=public" NEXT_PUBLIC_SITE_URL="http://localhost:3000" ADMIN_USER="admin" ADMIN_PASSWORD="changeme" ADMIN_SESSION_SECRET="" SESSION_SECRET="" ``` For random secrets, use: ```bash node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` ### Step 3: Database Setup ```bash # Create the database (make sure the DB named in DATABASE_URL already exists — e.g. `createdb craft2prints`) # then sync the schema to it npm run db:push # Load starter products npm run db:seed ``` ### Step 4: Start Development Server ```bash npm run dev ``` Open http://localhost:3000 in your browser. **Auto-reload on file changes:** The dev server watches your code and reloads automatically. If you change `prisma/schema.prisma`, run `npm run db:push` manually. --- ## Configuration Reference ### Essential Variables | Variable | Description | Example | |----------|-------------|---------| | `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@host:5432/craft2prints?schema=public` | | `ADMIN_USER` | Admin login username | `admin` | | `ADMIN_PASSWORD` | Admin login password | `changeme` | | `ADMIN_SESSION_SECRET` | Session signing key (random) | 32-byte hex string | | `SESSION_SECRET` | Customer session key (random) | 32-byte hex string | ### Stripe Configuration Get from https://dashboard.stripe.com (make sure you're in **test mode** first): ```env STRIPE_SECRET_KEY="sk_test_..." # From Developers > API keys STRIPE_WEBHOOK_SECRET="whsec_..." # From Stripe CLI (see below) ``` **Local Webhook Setup:** ```bash # Install Stripe CLI: https://docs.stripe.com/stripe-cli stripe login stripe listen --forward-to localhost:3000/api/webhook # Copy the whsec_... secret into .env ``` ### Email (SMTP) For order confirmations and password resets: ```env SMTP_HOST="smtp.gmail.com" SMTP_PORT="587" SMTP_USER="your-email@gmail.com" SMTP_PASSWORD="your-app-password" # Not your normal password! MAIL_FROM="Craft2Prints " ADMIN_NOTIFY_EMAIL="admin@example.com" # For photo request alerts ``` **For Gmail:** Generate an app password at https://myaccount.google.com/apppasswords (requires 2FA enabled). ### CAPTCHA (Cloudflare Turnstile) Protects public forms (registration, contact, custom requests): ```env NEXT_PUBLIC_TURNSTILE_SITE_KEY="..." # Get from dash.cloudflare.com TURNSTILE_SECRET_KEY="..." ``` If not configured, forms work without CAPTCHA. Optional, not required. ### Optional Variables ```env NEXT_PUBLIC_FACEBOOK_APP_ID="..." # For Messenger sharing NEXT_PUBLIC_SITE_URL="http://localhost:3000" # For design approval links ``` --- ## Database Management The app uses `prisma db push` (schema-driven sync), not versioned `prisma migrate` files — `prisma/schema.prisma` is the single source of truth for the database shape. ### Common Tasks **Sync the schema to the database:** ```bash npm run db:push ``` **Reset database (⚠️ deletes all data):** ```bash npx prisma db push --force-reset npm run db:seed ``` **Add new database tables/columns:** 1. Edit `prisma/schema.prisma` 2. Run `npm run db:push` to sync Because there's no migration history, renaming or dropping a column that already holds data is destructive — `db push` will refuse (or require `--accept-data-loss`) rather than silently drop it. For a column that needs to change shape on a live database, do it in three steps: add the new column alongside the old one (non-destructive `db push`), run a one-off script to copy/transform the data into it, update the app code to use the new column, then drop the old column in a second `db push` once you've confirmed nothing still depends on it. **Seed starter products:** ```bash npm run db:seed ``` Updating the seed is safe — it only touches the 6 starter products by their slug, leaving your custom products untouched. **Backup database:** ```bash pg_dump -h -p -U -d craft2prints -F p -f "database-backups/craft2prints_$(date +%Y-%m-%d_%H-%M-%S).sql" ``` `backup-database.ps1` in the repo root automates this — check its `$dbHost`/`$dbPort`/`$dbUser` match your actual `DATABASE_URL` before relying on it (it defaults to `localhost:5432`, which may not be where your database actually lives). **Backups only cover the database** — uploaded images/files live on disk under `public/uploads/` and need their own backup (see [Image & File Storage](#image--file-storage)). --- ## Image & File Storage Every uploaded image or document — gallery photos, product photos, custom-request photos, design proofs, design-approval previews, order design previews, purchase invoices, expense receipts, and images added inside the design tool — is saved as a **file on disk**, not as base64 text in the database. ### How it works 1. A file arrives either as a `multipart/form-data` upload (admin forms, customer photo submissions) or as a base64 data URL generated client-side by the design tool's canvas export (`stage.toDataURL()`/`canvas.toDataURL()`). 2. `src/lib/storage.ts` decodes/receives it, and — unless it's a PDF — runs it through [`sharp`](https://sharp.pixelplumbing.com/) to resize (capped per category, never upscaled) and re-encode it (JPEG for photos, lossless PNG where transparency must be preserved, e.g. print-ready design previews). 3. The result is written to `public/uploads////.` and the database column stores only the resulting path, e.g. `/uploads/gallery/2026/07/3f9c1e2a-....jpeg`. Categories and their presets (`IMAGE_PRESETS` in `src/lib/storage.ts`): | Category | Used for | Format | Max width | |---|---|---|---| | `gallery` | Public gallery photos | JPEG | 2000px | | `products` | Product photos (main + per-color) | JPEG, or PNG if the source has transparency | 2000px | | `custom-requests` | Customer-submitted photos | JPEG | 2000px | | `proofs` | Admin-uploaded design proofs | JPEG | 2000px | | `invoices` / `receipts` | Purchase/expense attachments | JPEG, or passthrough if PDF | 2000px | | `designs` | Print-ready design previews (transparent) | PNG (lossless) | 4000px | | `design-placements` | Design-on-product reference composites | JPEG | 2000px | | `design-uploads` | Photos a customer adds inside the design tool | JPEG, or PNG if transparent | 3000px | ### Why this matters operationally - **`public/uploads/` is gitignored but lives permanently on the server's disk.** It is not part of the database and is not covered by a `pg_dump` backup — back it up separately (a simple `rsync`/copy of the directory alongside your database backup schedule is enough). - **No web server configuration is needed.** Next.js serves everything under `public/` automatically, in both `next dev` and `next start` — the same mechanism that already serves `public/logo.png`. - **On a fresh server, make sure the Node process can write to `public/uploads/`** (it's created automatically on first upload, but the parent directory must be writable). - **Uploads endpoint for the design tool**: images added inside the design canvas are uploaded via `POST /api/design-uploads` as soon as they're added, rather than embedded as base64 in the design's saved JSON — keeping cart/order/design-approval records small regardless of how many images a customer adds. ### Troubleshooting **"sharp module could not be loaded" / native binding errors on the server** Sharp ships platform-specific native bindings — never copy `node_modules` between machines (e.g. from a Windows dev box to a Linux server). Run a fresh `npm install` on the target machine (the standard deploy flow below already does this). ## Feature Setup ### Stripe Payments 1. Create account at https://dashboard.stripe.com 2. Switch to **Test Mode** (top-right toggle) 3. Get **Secret Key** from Developers > API keys 4. Set `STRIPE_SECRET_KEY` in `.env` 5. Start Stripe webhook listener (see Configuration Reference above) 6. Test with card: `4242 4242 4242 4242`, any future expiry, any 3-digit CVC ### Email Notifications **Option 1: Gmail** 1. Enable 2-Factor Authentication 2. Generate app password at https://myaccount.google.com/apppasswords 3. Set SMTP variables in `.env` 4. Test by submitting a form that triggers email **Option 2: Other Providers** - Outlook, SendGrid, AWS SES, etc. — check their SMTP settings - Most follow the same pattern: username (email), password (app-specific) ### CAPTCHA Protection 1. Sign up at https://dash.cloudflare.com 2. Go to Turnstile > Add Site 3. Copy **Site Key** and **Secret Key** 4. Set in `.env`: ```env NEXT_PUBLIC_TURNSTILE_SITE_KEY="..." TURNSTILE_SECRET_KEY="..." ``` 5. Refresh the page — CAPTCHA should appear on forms --- ## Building for Production ### Build Process ```bash # Build the Next.js app npm run build # Start the production server npm start ``` The server listens on port 3000 by default. ### Build Output ``` .next/ # Compiled Next.js app (production build) .next/standalone/ # Self-contained server ``` ### Environment for Production Before deploying, update `.env` for production: ```env # Database (use production connection string) DATABASE_URL="postgresql://user:pass@prod-db:5432/craft2prints" # SECURITY: Change these! ADMIN_USER="" ADMIN_PASSWORD="" ADMIN_SESSION_SECRET="" SESSION_SECRET="" # Stripe (switch to LIVE keys, not test) STRIPE_SECRET_KEY="sk_live_..." STRIPE_WEBHOOK_SECRET="whsec_live_..." # Site URL NEXT_PUBLIC_SITE_URL="https://yourdomain.com" # Email (production SMTP) SMTP_HOST="..." SMTP_USER="..." SMTP_PASSWORD="..." MAIL_FROM="Craft2Prints " ``` --- ## Deployment ### Option 1: Self-Hosted (Recommended for Control) **Hardware Requirements:** - Server with Node.js 18+ support - 2GB+ RAM (more for concurrent traffic) - A reachable PostgreSQL database (local install, Docker container, or managed service) - Persistent disk for `public/uploads/` — uploaded images/files live here, not in the database (see [Image & File Storage](#image--file-storage)) **Setup:** 1. **Upload code to server** ```bash scp -r . user@server:/app/craft2prints ``` 2. **Install dependencies** ```bash ssh user@server cd /app/craft2prints npm install --production npm run build ``` 3. **Set up environment** ```bash cp .env.example .env # Edit .env with production values nano .env ``` 4. **Create database** ```bash npm run db:push npm run db:seed ``` 5. **Install process manager** (keep app running) ```bash npm install -g pm2 pm2 start "npm start" --name craft2prints pm2 startup pm2 save ``` 6. **Set up reverse proxy** (nginx) ```nginx server { listen 80; server_name yourdomain.com; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } ``` 7. **Enable HTTPS** (Let's Encrypt) ```bash sudo apt-get install certbot python3-certbot-nginx sudo certbot --nginx -d yourdomain.com ``` ### Option 2: Platform Services **Vercel** (easiest, free tier available) - Push to GitHub - Connect GitHub repo to Vercel - Environment variables in Vercel dashboard - Automatic deploys on push **Heroku** (deprecated but still works) - Use Procfile + buildpack - Set environment variables in Heroku dashboard **Railway, Render, etc.** - Similar to Vercel — push → deploy ### Option 3: Docker (Containerized) Create `Dockerfile`: ```dockerfile FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --production COPY . . RUN npm run build EXPOSE 3000 CMD ["npm", "start"] ``` ```bash docker build -t craft2prints . docker run -p 3000:3000 --env-file .env -v craft2prints-uploads:/app/public/uploads craft2prints ``` The `-v` volume mount is required — without it, `public/uploads/` lives inside the container's writable layer and every uploaded image/file is lost when the container is recreated. ### Post-Deployment Checklist - [ ] Admin password changed from default - [ ] Session secrets regenerated - [ ] Stripe keys switched to **live** (not test) - [ ] Stripe webhook configured in dashboard - [ ] HTTPS enabled with valid certificate - [ ] Email SMTP tested (send test order confirmation) - [ ] CAPTCHA working on public forms - [ ] Database backups scheduled - [ ] `public/uploads/` backups scheduled (separate from the database — see [Image & File Storage](#image--file-storage)) - [ ] Log monitoring set up - [ ] Rate limiting verified - [ ] Staging environment created for testing --- ## Troubleshooting ### Development **"Cannot find module '@prisma/client'"** ```bash npx prisma generate npm install ``` **Port 3000 already in use** ```bash npm run dev -- -p 3001 # Use port 3001 instead # Or find and kill process using port 3000 ``` **Blank product catalog** ```bash npm run db:seed # Load starter products ``` **Database out of sync / corrupted state** ```bash npx prisma db push --force-reset # ⚠️ drops and recreates all tables npm run db:seed ``` ### Production **App not starting** ```bash npm start # Check console for errors npm run build # Rebuild if necessary ``` **Email not sending** ```bash # Test SMTP configuration node -e "const smtp = require('nodemailer'); console.log(JSON.stringify(process.env, null, 2))" ``` **High memory usage** - Check for memory leaks: `node --inspect start` - Increase server RAM or split into multiple processes - Consider database optimization for large datasets **Database connection issues** ```bash # Test connection and list tables psql $DATABASE_URL -c '\dt' ``` --- ## Maintenance ### Regular Tasks **Daily:** - Monitor error logs - Check payment processing (Stripe dashboard) **Weekly:** - Backup database and `public/uploads/` - Review admin activity logs - Check storage usage **Monthly:** - Update npm dependencies: `npm outdated` - Review security advisories: `npm audit` - Analyze financial reports **Quarterly:** - Security audit - Performance optimization - Dependency updates ### Updating ```bash # Pull latest code git pull # Update dependencies npm install # Sync database schema npm run db:push # Rebuild if needed npm run build ``` --- ## Security Hardening ### Before Going Live 1. **Change default credentials** - Admin username/password - Session secrets (32-byte random strings) 2. **Enable HTTPS** - Get certificate from Let's Encrypt (free) - Set `NEXT_PUBLIC_SITE_URL` to `https://yourdomain.com` 3. **Rate Limiting** - Enabled by default on login/checkout - Adjust in code if needed 4. **CAPTCHA** - Recommended on all public forms - Get free keys from Cloudflare Turnstile 5. **Email** - Use app-specific passwords (not your actual password) - Enable SMTP authentication 6. **Backups** - Daily automated backups - Test restore process quarterly --- ## Support & Issues - **Questions?** Check this guide again or search existing issues - **Bug report?** Create GitHub issue with reproduction steps - **Feature request?** Open an issue for discussion --- *Last updated: 2026-07-22*