/** * BullMQ / ioredis connection for concierge queues. * DigitalOcean Managed Redis (port 25061) requires TLS — use rediss:// or REDIS_TLS=true. */ function parseRedisUrl(raw) { const url = String(raw || 'redis://127.0.0.1:6379').trim(); try { return new URL(url); } catch { return null; } } function shouldUseTls(parsed) { if (!parsed) return false; const force = String(process.env.REDIS_TLS || '').trim().toLowerCase() === '1' || String(process.env.REDIS_TLS || '').trim().toLowerCase() === 'true'; if (force) return true; if (parsed.protocol === 'rediss:') return true; if (parsed.port === '25061') return true; if (parsed.hostname && parsed.hostname.includes('ondigitalocean.com')) return true; return false; } /** * Options for BullMQ `connection` (ioredis). maxRetriesPerRequest must be null for workers. * @returns {import('ioredis').RedisOptions} */ function getBullMQConnection() { const raw = process.env.REDIS_URL || 'redis://127.0.0.1:6379'; const parsed = parseRedisUrl(raw); const conn = { maxRetriesPerRequest: null, enableReadyCheck: true, retryStrategy(times) { return Math.min(times * 300, 8000); }, }; if (!parsed) { return { ...conn, host: '127.0.0.1', port: 6379 }; } Object.assign(conn, { host: parsed.hostname, port: Number(parsed.port) || 6379, username: parsed.username || undefined, password: parsed.password ? decodeURIComponent(parsed.password) : undefined, }); if (parsed.pathname && parsed.pathname.length > 1) { const db = Number(parsed.pathname.slice(1)); if (!Number.isNaN(db)) conn.db = db; } if (shouldUseTls(parsed)) { conn.tls = {}; if (!parsed.port) conn.port = 25061; } return conn; } /** * Verify Redis is reachable at startup (workers). */ async function verifyRedisConnection() { const Redis = require('ioredis'); const opts = getBullMQConnection(); const client = new Redis(opts); try { await client.ping(); const tlsNote = opts.tls ? ' (TLS)' : ''; console.log(`[redis] connected: ${opts.host}:${opts.port}${tlsNote}`); } catch (err) { const hint = opts.tls ? '' : '\n Hint: DigitalOcean Managed Redis needs TLS. Set REDIS_TLS=true or use rediss:// in REDIS_URL.'; throw new Error(`Redis connection failed: ${err.message}${hint}`); } finally { client.disconnect(); } } module.exports = { getBullMQConnection, verifyRedisConnection, shouldUseTls, parseRedisUrl, };