← 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
M shopify/quote-api/appointments/deploy/api.designerwallcoverings.com.confM shopify/quote-api/appointments/migrations/appointments.sql
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 →