7 Commits
test ... master

Author SHA1 Message Date
0982be2f58 condition: service_healthy 2026-08-20 21:52:18 +02:00
ff70278be4 HOSTNAME: "0.0.0.0" 2026-08-20 21:45:48 +02:00
5171669d4d fix 401 2026-08-20 21:28:01 +02:00
5f0d5c0e72 Merge remote-tracking branch 'origin/test' 2026-08-20 17:58:51 +02:00
5541c0553c refactor: derive email sender address dynamically from SMTP_USER 2026-08-13 19:33:05 +02:00
172730cf0f SMTP_USER 2026-08-13 18:30:34 +02:00
9ccce7cbfd info instead of timo 2026-08-13 17:44:55 +02:00
2 changed files with 196 additions and 183 deletions

View File

@@ -52,6 +52,10 @@ services:
restart: unless-stopped
environment:
NODE_ENV: production
# Docker sets HOSTNAME=<container-id>, and the Next.js standalone server binds to
# exactly that one interface - localhost (healthcheck) and the container's other
# network addresses then answer with ECONNREFUSED.
HOSTNAME: "0.0.0.0"
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public
DIRECT_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public
REDIS_URL: redis://redis:6379
@@ -129,7 +133,10 @@ services:
context: ./scripts/social-worker
restart: unless-stopped
environment:
QRMASTER_API_BASE: http://web:3000
# Container name, not the `web` service alias: the staging stack joins this same
# external network and registers `web` as an alias too, so the alias can resolve
# to the staging app - which has a different INTERNAL_API_SECRET and answers 401.
QRMASTER_API_BASE: http://qrmaster-web:3000
INTERNAL_API_SECRET: ${INTERNAL_API_SECRET}
SOCIAL_MILESTONE_POSTING_ENABLED: ${SOCIAL_MILESTONE_POSTING_ENABLED:-false}
SOCIAL_WORKER_INTERVAL_SECONDS: ${SOCIAL_WORKER_INTERVAL_SECONDS:-10}
@@ -149,7 +156,9 @@ services:
SOCIAL_ASSET_ADMIN_KEY: ${SOCIAL_ASSET_ADMIN_KEY:-${TIKTOK_ADMIN_KEY:-}}
depends_on:
web:
condition: service_started
# Not service_started: the worker's first cycle would otherwise run while web is
# still applying migrations and booting, logging a connection error per channel.
condition: service_healthy
networks:
- qrmaster-network

View File

@@ -5,9 +5,13 @@ import { isSocialChannel, SOCIAL_CHANNELS, SocialChannel } from '@/lib/social-mi
export const dynamic = 'force-dynamic';
// The worker trims its own copy of the secret before sending it, so a stray
// trailing space or CR in the compose env file would otherwise show up here as a
// permanent 401 while both sides still look identical in a hash comparison.
function isAuthorized(request: NextRequest) {
const secret = process.env.INTERNAL_API_SECRET;
return Boolean(secret) && request.headers.get('authorization') === `Bearer ${secret}`;
const secret = process.env.INTERNAL_API_SECRET?.trim();
const presented = request.headers.get('authorization')?.trim().replace(/^Bearer\s+/i, '');
return Boolean(secret) && presented === secret;
}
function approvalDelayHours() {