[object Object]

← back to Designer Wallcoverings

appointments LIVE at api.designerwallcoverings.com: migration re-owns appt_ to dw_admin; vhost corrected to IP-specific :443 (SNI fix). Backend deployed + verified e2e over HTTPS.

abe1470ca4b376f85ba8232469fd81331eeef473 · 2026-06-29 11:24:03 -0700 · Steve Abrams

Files touched

Diff

commit abe1470ca4b376f85ba8232469fd81331eeef473
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 29 11:24:03 2026 -0700

    appointments LIVE at api.designerwallcoverings.com: migration re-owns appt_ to dw_admin; vhost corrected to IP-specific :443 (SNI fix). Backend deployed + verified e2e over HTTPS.
---
 .../deploy/api.designerwallcoverings.com.conf      | 57 ++++++++--------------
 .../appointments/migrations/appointments.sql       | 15 ++++++
 2 files changed, 35 insertions(+), 37 deletions(-)

diff --git a/shopify/quote-api/appointments/deploy/api.designerwallcoverings.com.conf b/shopify/quote-api/appointments/deploy/api.designerwallcoverings.com.conf
index 1c0ee138..a14232aa 100644
--- a/shopify/quote-api/appointments/deploy/api.designerwallcoverings.com.conf
+++ b/shopify/quote-api/appointments/deploy/api.designerwallcoverings.com.conf
@@ -1,58 +1,33 @@
 # ─────────────────────────────────────────────────────────────────────────────
-# api.designerwallcoverings.com — Kamatera nginx vhost (Option A edge routing)
-# Fronts the koroseal-quote-api app (127.0.0.1:3001) for the DW storefront's
-# cross-origin API calls: appointments + the existing koroseal-quote/sku-inquiry.
+# DEPLOYED 2026-06-29 as /etc/nginx/sites-available/designerwallcoverings.com-api
+# (an existing vhost already owned server_name api.designerwallcoverings.com; this
+# is the corrected content now live). Fronts koroseal-quote-api :3001 for all DW
+# storefront cross-origin API calls + the appointment confirmation page.
 #
-# INSTALL (on Kamatera, as root via `ssh my-server`):
-#   1. Put this file at /etc/nginx/sites-available/api.designerwallcoverings.com
-#   2. ln -s /etc/nginx/sites-available/api.designerwallcoverings.com \
-#            /etc/nginx/sites-enabled/api.designerwallcoverings.com
-#   3. nginx -t && systemctl reload nginx        # serves :80 immediately (for ACME + testing)
-#   4. certbot --nginx -d api.designerwallcoverings.com --redirect -m info@designerwallcoverings.com --agree-tos -n
-#      (certbot rewrites this file to add the 443 server + cert paths + the 80→443 redirect)
-#
-# PREREQ: DNS A record api.designerwallcoverings.com → 45.61.58.125 must resolve first.
+# GOTCHA baked in: must listen on the box's SPECIFIC IP (45.61.58.125:443), NOT bare
+# `443` — otherwise it lands in the wrong listen-socket group on this ~40-vhost box and
+# SNI never matches api.designerwallcoverings.com (it silently served the default
+# vhost's cert). Cert issued by: certbot --nginx -d api.designerwallcoverings.com.
 # ─────────────────────────────────────────────────────────────────────────────
-
 server {
-    listen 80;
-    listen [::]:80;
     server_name api.designerwallcoverings.com;
 
-    # Let certbot answer the ACME http-01 challenge.
-    location /.well-known/acme-challenge/ { root /var/www/html; }
-
-    # Reasonable upload ceiling for booking/inquiry payloads.
-    client_max_body_size 256k;
-
-    # ── Appointments API (router sets its own CORS: Allow-Origin *) ──
     location /api/appointments/ {
         proxy_pass http://127.0.0.1:3001;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header X-Forwarded-Proto $scheme;
-        proxy_connect_timeout 10s;
-        proxy_send_timeout 30s;
         proxy_read_timeout 30s;
     }
-
-    # ── Appointment confirmation page + .ics (customer-facing, no /api prefix) ──
     location /appointments/ {
         proxy_pass http://127.0.0.1:3001;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header X-Forwarded-Proto $scheme;
-        proxy_connect_timeout 10s;
-        proxy_send_timeout 30s;
         proxy_read_timeout 30s;
     }
-
-    # ── Existing DW endpoints (were 404 on the storefront; now reachable here) ──
-    # NOTE: these use the koroseal app's APP-LEVEL cors({origin: CORS_ORIGIN}); set
-    # CORS_ORIGIN in the app .env to the storefront's primary domain (allow apex AND
-    # www if both are served) or the browser blocks the cross-origin POST.
     location = /api/koroseal-quote {
         proxy_pass http://127.0.0.1:3001;
         proxy_set_header Host $host;
@@ -67,10 +42,18 @@ server {
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header X-Forwarded-Proto $scheme;
     }
-
-    # Health passthrough (optional convenience).
     location = /health { proxy_pass http://127.0.0.1:3001; }
-
-    # Everything else on this host: nothing to serve.
     location / { return 404; }
+
+    listen 45.61.58.125:443 ssl; # managed by Certbot
+    ssl_certificate /etc/letsencrypt/live/api.designerwallcoverings.com/fullchain.pem; # managed by Certbot
+    ssl_certificate_key /etc/letsencrypt/live/api.designerwallcoverings.com/privkey.pem; # managed by Certbot
+    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+}
+server {
+    if ($host = api.designerwallcoverings.com) { return 301 https://$host$request_uri; }
+    listen 80;
+    server_name api.designerwallcoverings.com;
+    return 404;
 }
diff --git a/shopify/quote-api/appointments/migrations/appointments.sql b/shopify/quote-api/appointments/migrations/appointments.sql
index eb68504c..1da6cf6e 100644
--- a/shopify/quote-api/appointments/migrations/appointments.sql
+++ b/shopify/quote-api/appointments/migrations/appointments.sql
@@ -70,3 +70,18 @@ INSERT INTO appt_availability (owner_email, day_of_week, start_min, end_min, slo
 SELECT 'info@designerwallcoverings.com', d, 540, 960, 60
 FROM generate_series(1, 5) AS d
 ON CONFLICT (owner_email, day_of_week, start_min, end_min) DO NOTHING;
+
+-- The app connects as dw_admin, but this file is run via `sudo -u postgres` on Kamatera
+-- (the standard pattern), which would leave the objects postgres-owned → the app gets
+-- "permission denied". Reassign ownership to dw_admin when that role exists. Idempotent.
+DO $$
+BEGIN
+  IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'dw_admin') THEN
+    ALTER TABLE appt_appointments  OWNER TO dw_admin;
+    ALTER TABLE appt_availability   OWNER TO dw_admin;
+    ALTER TABLE appt_oauth_tokens   OWNER TO dw_admin;
+    ALTER TABLE appt_oauth_state    OWNER TO dw_admin;
+    ALTER SEQUENCE appt_appointments_id_seq OWNER TO dw_admin;
+    ALTER SEQUENCE appt_availability_id_seq  OWNER TO dw_admin;
+  END IF;
+END $$;

← 66541e4e product min-qty: step=1 — minimum is a floor, above it order  ·  back to Designer Wallcoverings  ·  product min-qty: target real mm_ quantity widget + single-op 9fcc5ff8 →