From 37c6c497ff2d9cb865fccbf09b39960d32d03ee1 Mon Sep 17 00:00:00 2001 From: Deep Koluguri Date: Sun, 5 Apr 2026 16:38:37 -0400 Subject: [PATCH] fixing link issues --- backend/.env.example | 4 +- backend/src/routes/publicShare.js | 135 ++++++++++++++++++------------ backend/src/routes/share.js | 3 +- 3 files changed, 86 insertions(+), 56 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index d56eed1..845a13e 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -23,10 +23,10 @@ SQLITE_STORAGE=./data/luckychit.sqlite JWT_SECRET=change-me-in-production -# Public draw links (/share/draw/...): origin WITHOUT /api. Optional if Nginx sends Host + X-Forwarded-Proto. +# Public draw links: /share/draw?t= (origin WITHOUT /api). Optional if Nginx sends Host + X-Forwarded-Proto. # PUBLIC_BASE_URL=https://your-domain.com -# Signed tokens for /share/draw/:token — defaults to permanent links (no JWT expiry) +# Signed tokens for /share/draw — defaults to permanent links (no JWT expiry) # Set to false to use SHARE_TOKEN_TTL_DAYS instead # SHARE_DRAW_PERMANENT_LINKS=true # SHARE_TOKEN_TTL_DAYS=365 diff --git a/backend/src/routes/publicShare.js b/backend/src/routes/publicShare.js index d22bb9f..83aa133 100644 --- a/backend/src/routes/publicShare.js +++ b/backend/src/routes/publicShare.js @@ -25,61 +25,59 @@ function formatInr(amount) { } /** - * Public, unauthenticated page — opened via signed token from POST /api/share/draw-link. - * Anyone with the link can view draw results (no login). + * Render public draw page from signed JWT (same secret as POST /api/share/draw-link). + * Token in query avoids ENAMETOOLONG / proxy issues from huge path segments. */ -router.get('/draw/:token', async (req, res) => { +async function renderDrawSharePage(req, res, token) { + const shareSecret = process.env.SHARE_TOKEN_SECRET || process.env.JWT_SECRET; + if (!shareSecret) { + return res.status(500).send('Share is not configured.'); + } + + let payload; try { - const { token } = req.params; - const shareSecret = process.env.SHARE_TOKEN_SECRET || process.env.JWT_SECRET; - if (!shareSecret) { - return res.status(500).send('Share is not configured.'); + payload = jwt.verify(token, shareSecret); + } catch { + return res.status(400).send('Invalid or expired share link.'); + } + + if (!payload?.type || payload.type !== 'draw_result' || !payload.drawId) { + return res.status(400).send('Invalid share link.'); + } + + const draw = await MonthlyDraw.findByPk(payload.drawId, { + include: [ + { model: ChitGroup, attributes: ['id', 'name'] }, + { model: User, as: 'winner', attributes: ['id', 'full_name', 'mobile_number'] } + ] + }); + + if (!draw) { + return res.status(404).send('Draw not found.'); + } + + const totalMembers = await GroupMember.count({ + where: { group_id: draw.group_id, status: 'active' } + }); + + const title = `Draw Result — ${draw.ChitGroup ? draw.ChitGroup.name : 'Chit Group'}`; + const winnerName = draw.winner ? draw.winner.full_name : 'TBD'; + const groupName = draw.ChitGroup ? draw.ChitGroup.name : 'Chit Group'; + const subtitle = `${draw.month}/${draw.year} • Winner: ${winnerName}`; + + let drawWhen = ''; + try { + const d = draw.draw_date ? new Date(draw.draw_date) : null; + if (d && !Number.isNaN(d.getTime())) { + drawWhen = d.toLocaleString('en-IN', { dateStyle: 'medium', timeStyle: 'short' }); } + } catch { + drawWhen = ''; + } - let payload; - try { - payload = jwt.verify(token, shareSecret); - } catch { - return res.status(400).send('Invalid or expired share link.'); - } + const prizeHtml = formatInr(draw.prize_amount); - if (!payload?.type || payload.type !== 'draw_result' || !payload.drawId) { - return res.status(400).send('Invalid share link.'); - } - - const draw = await MonthlyDraw.findByPk(payload.drawId, { - include: [ - { model: ChitGroup, attributes: ['id', 'name'] }, - { model: User, as: 'winner', attributes: ['id', 'full_name', 'mobile_number'] } - ] - }); - - if (!draw) { - return res.status(404).send('Draw not found.'); - } - - const totalMembers = await GroupMember.count({ - where: { group_id: draw.group_id, status: 'active' } - }); - - const title = `Draw Result — ${draw.ChitGroup ? draw.ChitGroup.name : 'Chit Group'}`; - const winnerName = draw.winner ? draw.winner.full_name : 'TBD'; - const groupName = draw.ChitGroup ? draw.ChitGroup.name : 'Chit Group'; - const subtitle = `${draw.month}/${draw.year} • Winner: ${winnerName}`; - - let drawWhen = ''; - try { - const d = draw.draw_date ? new Date(draw.draw_date) : null; - if (d && !Number.isNaN(d.getTime())) { - drawWhen = d.toLocaleString('en-IN', { dateStyle: 'medium', timeStyle: 'short' }); - } - } catch { - drawWhen = ''; - } - - const prizeHtml = formatInr(draw.prize_amount); - - const html = ` + const html = ` @@ -136,9 +134,40 @@ router.get('/draw/:token', async (req, res) => { `; - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.setHeader('Cache-Control', 'public, max-age=300'); - return res.status(200).send(html); + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.setHeader('Cache-Control', 'public, max-age=300'); + return res.status(200).send(html); +} + +/** + * Preferred: GET /share/draw?t= — short path; avoids ENAMETOOLONG behind Nginx/static rules. + */ +router.get('/draw', async (req, res) => { + try { + const raw = req.query.t ?? req.query.token; + const token = typeof raw === 'string' ? raw.trim() : ''; + if (!token) { + return res + .status(400) + .send('Missing token. Use the full link from WhatsApp (it should include ?t=…).'); + } + return await renderDrawSharePage(req, res, token); + } catch (error) { + console.error('Public draw share error:', error); + return res.status(500).send('Server error.'); + } +}); + +/** + * Legacy: GET /share/draw/:token — may fail on some hosts (path segment too long → ENAMETOOLONG). + */ +router.get('/draw/:token', async (req, res) => { + try { + const token = String(req.params.token || '').trim(); + if (!token) { + return res.status(400).send('Missing token.'); + } + return await renderDrawSharePage(req, res, token); } catch (error) { console.error('Public draw share error:', error); return res.status(500).send('Server error.'); diff --git a/backend/src/routes/share.js b/backend/src/routes/share.js index 6f0e786..61f4aa0 100644 --- a/backend/src/routes/share.js +++ b/backend/src/routes/share.js @@ -159,7 +159,8 @@ router.post('/draw-link', auth, requireManager, async (req, res) => { 'PUBLIC_BASE_URL not configured (set to your site origin without /api, e.g. https://chitfund.deepteklabs.com)' }); } - const shareUrl = `${publicBaseUrl}/share/draw/${token}`; + // Query param keeps URL path short (avoids ENAMETOOLONG / static-file lookup on long JWT paths). + const shareUrl = `${publicBaseUrl}/share/draw?t=${encodeURIComponent(token)}`; const message = WhatsAppHelper.generateDrawResult( draw.ChitGroup,