### Prepare and Run Marketplace Installer Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/infra/setup/README.md Copy the example configuration file and edit it with your specific details before running the main installation script. ```bash # 1. Fill in config cp infra/setup/config.example.sh infra/setup/config.sh $EDITOR infra/setup/config.sh # 2. Run installer bash infra/setup/install.sh ``` -------------------------------- ### File Layout of infra/setup Directory Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/infra/setup/README.md Overview of the files within the `infra/setup` directory, indicating their purpose in the installation process. ```text infra/setup/ ├── README.md ← you are here ├── config.example.sh ← template (commit this) ├── config.sh ← your real values (.gitignored — never commit) ├── install.sh ← run from your laptop ├── bootstrap.sh ← runs on droplet (uploaded by install.sh) ├── install-services.sh ← runs on droplet (systemd + Caddy) ├── install-cron.sh ← runs on droplet (cron jobs) └── uninstall.sh ← reverses install.sh (optional --nuke-do) ``` -------------------------------- ### Build Shop Droplet Snapshot Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/setup-do-fresh.md Execute the build-snapshot script to create a new snapshot for the shop droplet. This script automates the process of creating a temporary builder droplet, installing Docker, pulling the shop image, and taking a snapshot. ```bash cd /opt/marketplace export DIGITALOCEAN_TOKEN= export SHOP_IMAGE=registry.digitalocean.com/marketplace/shop-app:latest export DO_REGION=sgp1 export DO_SSH_KEY_ID= # optional, ไว้ debug bash infra/shop-droplet/build-snapshot.sh ``` -------------------------------- ### Build Snapshot for Shop Droplet Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/infra/shop-droplet/README.md Builds a fresh DigitalOcean snapshot for the shop droplet. This process involves installing Docker, pulling the shop image, creating a snapshot, and then destroying a temporary droplet. Set DIGITALOCEAN_TOKEN and SHOP_IMAGE environment variables before running. ```bash export DIGITALOCEAN_TOKEN=... export SHOP_IMAGE=registry.digitalocean.com//shop-app:latest bash infra/shop-droplet/build-snapshot.sh ``` -------------------------------- ### Configure and Start Control Plane Service Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/setup-do-fresh.md Set up a systemd service to manage the marketplace control plane container. This includes pulling the latest image, starting, stopping, and restarting the container. ```bash cat > /etc/systemd/system/marketplace-control.service <<'EOF' [Unit] Description=Marketplace control plane After=docker.service Requires=docker.service [Service] Restart=always RestartSec=5 ExecStartPre=-/usr/bin/docker stop marketplace-control ExecStartPre=-/usr/bin/docker rm marketplace-control ExecStartPre=/usr/bin/docker pull registry.digitalocean.com/marketplace/control-plane:latest ExecStart=/usr/bin/docker run --rm --name marketplace-control \ --env-file /etc/marketplace/control.env \ -p 127.0.0.1:3000:3000 \ registry.digitalocean.com/marketplace/control-plane:latest ExecStop=/usr/bin/docker stop marketplace-control [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable --now marketplace-control systemctl status marketplace-control ``` -------------------------------- ### Build Shop Droplet Snapshot Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/first-time-setup.md Create a boot snapshot for new shop droplets using a pre-built Docker image. This significantly speeds up provisioning. Ensure DIGITALOCEAN_TOKEN and SHOP_IMAGE are set. ```bash export DIGITALOCEAN_TOKEN=... export SHOP_IMAGE=registry.digitalocean.com//shop-app:latest bash infra/shop-droplet/build-snapshot.sh ``` -------------------------------- ### Build and Push Shop Docker Image Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/first-time-setup.md Build the Docker image for the shop application and push it to a container registry. Replace `` with your actual registry name. ```bash docker build -f infra/shop-droplet/Dockerfile.shop \ -t registry.digitalocean.com//shop-app:latest . docker push registry.digitalocean.com//shop-app:latest ``` -------------------------------- ### Order Paid Email Hook Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/six-store-golive-audit.md Example of a transactional email hook that passes the store's contact email as the Reply-To address. This is part of the existing implementation for per-store email customization. ```typescript import { send } from "lib/transactional-email/send.ts"; // ... other imports and logic ... await send({ template: "order-paid", to: customer.email, replyTo: store.contactEmail, // Use store's contact email for Reply-To // ... other options ... }); ``` -------------------------------- ### Build and Push Shop App Image Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/infra/shop-droplet/README.md Builds the shop application Docker image and pushes it to a specified registry. Ensure you replace `` with your actual registry path. ```bash docker build -f infra/shop-droplet/Dockerfile.shop -t registry.digitalocean.com//shop-app:latest . docker push registry.digitalocean.com//shop-app:latest ``` -------------------------------- ### Multi-tenant Provisioning Lifecycle Stages Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/README.md Diagram illustrating the lifecycle stages of a shop's provisioning process, from admin approval to active status. ```text admin approves store │ ▼ PENDING ──► CREATING_DROPLET ──► CONFIGURING_DNS ──► DEPLOYING_APP │ ▼ READY_FOR_WHITELIST │ admin emails PG, PG confirms │ ▼ WHITELIST_REQUESTED │ admin clicks "Confirm" button │ ▼ ACTIVE ``` -------------------------------- ### Create First Admin User via Direct SQL Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/setup-do-fresh.md Alternatively, promote a user to ADMIN role by directly executing an SQL UPDATE statement against the database. ```bash psql "$DATABASE_URL" -c "UPDATE \"User\" SET role='ADMIN' WHERE email='you@example.com';" ``` -------------------------------- ### Deploy Prisma Migrations Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/first-time-setup.md Apply database migrations to your control plane using the Prisma CLI. Ensure your DATABASE_URL is set correctly. ```bash npx prisma migrate deploy ``` -------------------------------- ### Multi-tenant Provisioning Architecture Diagram Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/README.md Visual representation of the multi-tenant provisioning architecture, showing the control plane and individual shop droplets. ```text ┌─────────────────────────────────────────────────────────────────────┐ │ Control plane (1 droplet) │ │ │ │ ┌────────────────┐ ┌─────────────────────┐ ┌──────────────────┐ │ │ │ Marketplace │ │ Provisioner │ │ Managed │ │ │ │ admin + API │──│ orchestrator + │──│ Postgres │ │ │ │ (this repo) │ │ queue worker │ │ (shared) │ │ │ └────────────────┘ └─────────────────────┘ └──────────────────┘ │ │ │ ▲ │ │ │ │ HTTPS, internal-bearer │ └──────────────────────────────────│──│────────────────────────────────┘ │ │ DO + CF APIs ──┐ │ │ ▼ ▼ │ caddy-ask, agent heartbeat ┌──────────────────────┴───────────────────────┐ │ Shop droplets │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ shop A │ │ shop B │ ... │ │ │ IP 159.x.1 │ │ IP 159.x.2 │ │ │ │ Caddy + app │ │ Caddy + app │ │ │ └──────────────┘ └──────────────┘ │ └────────────────────────────────────────────────┘ ▲ │ customers ───────┘ via Cloudflare DNS (grey cloud) ``` -------------------------------- ### Create First Admin User via Prisma Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/setup-do-fresh.md Promote a user to ADMIN role by executing an SQL UPDATE statement through Prisma's execute command. Ensure you are logged in via Google OAuth first. ```bash docker exec -it marketplace-control sh -c \ "echo \"UPDATE \\\"User\\\" SET role='ADMIN' WHERE email='you@example.com';\" | npx prisma db execute --stdin" ``` -------------------------------- ### Address Backfill Preparation and Verification Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/six-store-golive-audit.md Prepares for address backfill by counting existing addresses and then updating addresses missing a store ID. Ensures data integrity before applying migrations to production. Run the first count query before `prisma migrate deploy` on prod. ```sql -- Section 2: Address backfill prep (run BEFORE prisma migrate deploy on prod) SELECT COUNT(*) FROM "Address"; -- if > 0, run per-user backfill picking latest-order's store: -- UPDATE "Address" a SET "storeId" = ( -- SELECT o."storeId" FROM "Order" o -- WHERE o."userId" = a."userId" AND o."storeId" IS NOT NULL -- ORDER BY o."createdAt" DESC LIMIT 1 -- ) WHERE a."storeId" IS NULL; SELECT COUNT(*) FROM "Address" WHERE "storeId" IS NULL; -- must be 0 ``` -------------------------------- ### Run Prisma Database Migrations Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/setup-do-fresh.md Execute database migrations using Prisma on the control plane droplet. This command pulls the latest control-plane image and applies pending migrations. ```bash cd /opt/marketplace docker run --rm \ --env-file /etc/marketplace/control.env \ registry.digitalocean.com/marketplace/control-plane:latest \ npx prisma migrate deploy ``` -------------------------------- ### Apply SQL Migrations Directly Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/setup-do-fresh.md Alternatively, apply database schema changes directly using SQL commands if not using Prisma migrations. ```bash psql "$DATABASE_URL" -f /opt/marketplace/docs/multi-tenant-provisioning/migration.sql ``` -------------------------------- ### Query for Failed Provisioning Jobs Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/runbook.md Use this SQL query to find the last 5 failed provisioning jobs for deployments stuck in the CREATING_DROPLET state. This helps diagnose common issues like invalid tokens or region quotas. ```sql SELECT job.* FROM "ProvisioningJob" job JOIN "ShopDeployment" d ON d.id = job."deploymentId" WHERE d.status = 'CREATING_DROPLET' AND job.status = 'FAILED' ORDER BY job."finishedAt" DESC LIMIT 5; ``` -------------------------------- ### SSH into Droplet and Check Logs Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/runbook.md Use these commands to SSH into a droplet and inspect system logs and Docker container status when a deployment is stuck in DEPLOYING_APP. ```bash ssh -i ~/.ssh/your-do-key root@ journalctl -u cloud-final --no-pager docker compose -f /opt/marketplace-shop/docker-compose.yml ps docker compose -f /opt/marketplace-shop/docker-compose.yml logs shop --tail=200 ``` -------------------------------- ### SQL Query for Store Template and Theme Variant Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/six-store-golive-audit.md Run this SQL query on the production database to retrieve the slug, name, templateId, landingThemeVariant, render_path, approvalStatus, and customDomain for six specific stores. This information is crucial for determining their family coverage. ```sql SELECT slug, name, "templateId", "landingThemeVariant", CASE WHEN "landingBlocks" IS NULL THEN 'default' WHEN "templateId" IS NOT NULL THEN 'react-template' ELSE 'ai-multi-page' END AS render_path, "approvalStatus", "customDomain" FROM "Store" WHERE slug IN ('minimop24', 'zugarbox', 'ergobodies', 'Powerpuff678', 'casethep', 'bikini551') ORDER BY slug; ``` -------------------------------- ### Before: Default Cart and Checkout Adapters Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/components/storefront/themes/_shared/MIGRATION-thai-cart-checkout.md Shows the original registry entry using default English/USD cart and checkout adapters. These adapters do not integrate with the live Zustand cart or the /api/checkout endpoint. ```typescript // lib/templates/registry.ts import { makeCartAdapter } from '@/components/storefront/themes/_shared/cart-adapter'; import { makeCheckoutAdapter } from '@/components/storefront/themes/_shared/checkout-adapter'; 'hinoki-apothecary': { id: 'hinoki-apothecary', // ... pages: { home: enhanceHomepage(HinokiHomepageAdapter, '06'), catalog: makeCatalogAdapter('04'), pdp: makePdpAdapter('04', '05'), cart: makeCartAdapter('01'), // ← English/USD, no zustand checkout: makeCheckoutAdapter('04'), // ← English/USD, no /api/checkout }, }, ``` -------------------------------- ### Update Environment and Restart Control Plane Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/setup-do-fresh.md After building a new shop droplet snapshot, update the DO_SHOP_SNAPSHOT_ID in the control-plane environment file and restart the marketplace-control service to apply the changes. ```bash nano /etc/marketplace/control.env # แก้ DO_SHOP_SNAPSHOT_ID=192837465 systemctl restart marketplace-control ``` -------------------------------- ### Secure Environment File Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/setup-do-fresh.md Ensure the control.env file is not world-readable to protect sensitive credentials. ```bash chmod 600 /etc/marketplace/control.env ``` -------------------------------- ### Customizing makeThaiCartAdapter Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/components/storefront/themes/_shared/MIGRATION-thai-cart-checkout.md Demonstrates how to customize the Thai cart adapter using an optional configuration object. This allows overriding defaults for palette, shipping, text, and trust strip elements. ```typescript makeThaiCartAdapter({ // Palette override — any subset of fields. Anything omitted // resolves to the var(--shop-*) cascade the layout already // seeds, so most themes pass no palette at all. palette: { primary: 'var(--shop-primary)', // brand color surfaceMuted: 'var(--shop-muted)', // softer card background }, freeShippingThreshold: 990, // default 990 flatShippingTHB: 50, // default 50 heading: 'ตะกร้าสินค้า', // default "ตะกร้าสินค้า" emptyStateMessage: 'ตะกร้าของคุณยังว่างอยู่', emptyStateSubMessage: 'เริ่มเลือกสินค้าที่คุณชอบ', checkoutCtaLabel: 'ดำเนินการชำระเงิน', // Trust strip — three pills below the order summary. trustStrip: [ { icon: 'truck', label: 'ส่งฟรี ฿990+' }, { icon: 'rotate', label: 'คืนได้ 7 วัน' }, { icon: 'banknote', label: 'COD ได้' }, ], showCouponField: true, // /api/coupons/validate showTrustStrip: true, }); ``` -------------------------------- ### After: Migrated Thai Cart and Checkout Adapters Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/components/storefront/themes/_shared/MIGRATION-thai-cart-checkout.md Illustrates the updated registry entry using the new Thai/THB cart and checkout adapters. These adapters are wired to use the live Zustand cart and support the 4-step Thai checkout flow. ```typescript // lib/templates/registry.ts import { makeThaiCartAdapter } from '@/components/storefront/themes/_shared/thai-cart-adapter'; import { makeThaiCheckoutAdapter } from '@/components/storefront/themes/_shared/thai-checkout-adapter'; 'hinoki-apothecary': { id: 'hinoki-apothecary', // ... pages: { home: enhanceHomepage(HinokiHomepageAdapter, '06'), catalog: makeCatalogAdapter('04'), pdp: makePdpAdapter('04', '05'), cart: makeThaiCartAdapter(), // ← Thai/THB, useCart wired checkout: makeThaiCheckoutAdapter(), // ← 4-step Thai checkout }, }, ``` -------------------------------- ### SQL Query for Whitelist Audit Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/payment-whitelist.md Use this SQL query to retrieve a report of all IPs currently registered with the payment provider for active shop deployments. Reconcile this data against the provider's dashboard quarterly. ```sql SELECT s.slug, s."companyName", s."taxId", d."publicIpv4", d."paymentWhitelistStatus", d."paymentWhitelistConfirmedAt" FROM "ShopDeployment" d JOIN "Store" s ON s.id = d."storeId" WHERE d.status = 'ACTIVE' ORDER BY d."paymentWhitelistConfirmedAt"; ``` -------------------------------- ### Store Template and Rendering Path Audit Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/six-store-golive-audit.md Retrieves store slugs, names, template IDs, and determines the rendering path based on landing block and template ID configurations. Useful for auditing store configurations before go-live. ```sql -- Section 1: template / family mapping SELECT slug, name, "templateId", "landingThemeVariant", CASE WHEN "landingBlocks" IS NULL THEN 'default' WHEN "templateId" IS NOT NULL THEN 'react-template' ELSE 'ai-multi-page' END AS render_path, "approvalStatus", "customDomain" FROM "Store" WHERE slug IN ('minimop24','zugarbox','ergobodies','Powerpuff678','casethep','bikini551') ORDER BY slug; ``` -------------------------------- ### Configure Caddy as a Reverse Proxy Source: https://github.com/uptojig/marketplace/blob/feat/multi-tenant-provisioning/docs/multi-tenant-provisioning/setup-do-fresh.md Configure Caddy to serve traffic for your marketplace domain and proxy requests to the control plane service running on port 3000. ```bash cat > /etc/caddy/Caddyfile < /dev/null 2>&1 ```