mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
fix(broadcast): track route paths + shared-branding tsconfig
Two fixes surfaced by the end-to-end smoke test.
1. broadcast-track.ts: inner route paths double-prefixed.
Routes were declared as '/track/open/:token' etc, then
mounted at '/api/v1/track', yielding '/api/v1/track/track/open/:token'
— every tracking endpoint returned 404. Dropped the redundant
'/track/' prefix so the full path is now
'/api/v1/track/{open,click,unsubscribe}/:token' as the
orchestrator + client both expect.
Verified with live curl:
- /track/open/BAD → 200 image/gif 42 bytes (graceful no-signal)
- /track/click/?url missing → 400 missing url
- /track/click?url=javascript: → 400 bad url
- /track/click?url=https://ok + bad token → 302 graceful
- /track/unsubscribe/BAD GET → 400 HTML
- /track/unsubscribe/BAD POST → 400 (RFC 8058)
2. shared-branding/tsconfig.json: allowImportingTsExtensions
missing. shared-types/src/index.ts uses explicit .ts
imports (intentional, for Tailwind's module resolver); any
downstream tsconfig without allowImportingTsExtensions emits
8 errors. shared-auth already had this fix — shared-branding
gets the same treatment. noEmit:true is set, so no rewrite
flag needed.
Verified: shared-branding pnpm check → 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
04293ed5e7
commit
12be75e6a6
2 changed files with 5 additions and 4 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export function createBroadcastTrackRoutes(db: Database, trackingSecret: string,
|
||||||
* GET /track/open/:token — 1×1 pixel. Always returns the pixel even
|
* GET /track/open/:token — 1×1 pixel. Always returns the pixel even
|
||||||
* on bad tokens so there's no signal to whoever's probing.
|
* on bad tokens so there's no signal to whoever's probing.
|
||||||
*/
|
*/
|
||||||
app.get('/track/open/:token', async (c) => {
|
app.get('/open/:token', async (c) => {
|
||||||
const token = c.req.param('token');
|
const token = c.req.param('token');
|
||||||
const payload = verifyToken(token, trackingSecret);
|
const payload = verifyToken(token, trackingSecret);
|
||||||
if (!payload) return pixelResponse();
|
if (!payload) return pixelResponse();
|
||||||
|
|
@ -86,7 +86,7 @@ export function createBroadcastTrackRoutes(db: Database, trackingSecret: string,
|
||||||
* graceful-fall-through on verification failure so a broken token
|
* graceful-fall-through on verification failure so a broken token
|
||||||
* doesn't strand the recipient on a dead page.
|
* doesn't strand the recipient on a dead page.
|
||||||
*/
|
*/
|
||||||
app.get('/track/click/:token', async (c) => {
|
app.get('/click/:token', async (c) => {
|
||||||
const token = c.req.param('token');
|
const token = c.req.param('token');
|
||||||
const targetUrl = c.req.query('url');
|
const targetUrl = c.req.query('url');
|
||||||
if (!targetUrl) return c.text('missing url', 400);
|
if (!targetUrl) return c.text('missing url', 400);
|
||||||
|
|
@ -121,7 +121,7 @@ export function createBroadcastTrackRoutes(db: Database, trackingSecret: string,
|
||||||
* so a plain anchor link works for older clients — but we still
|
* so a plain anchor link works for older clients — but we still
|
||||||
* persist the unsubscribe on GET because the user actively clicked.
|
* persist the unsubscribe on GET because the user actively clicked.
|
||||||
*/
|
*/
|
||||||
app.get('/track/unsubscribe/:token', async (c) => {
|
app.get('/unsubscribe/:token', async (c) => {
|
||||||
const token = c.req.param('token');
|
const token = c.req.param('token');
|
||||||
const payload = verifyToken(token, trackingSecret);
|
const payload = verifyToken(token, trackingSecret);
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
|
|
@ -160,7 +160,7 @@ export function createBroadcastTrackRoutes(db: Database, trackingSecret: string,
|
||||||
* Same effect as GET but returns 204 so the client doesn't show a
|
* Same effect as GET but returns 204 so the client doesn't show a
|
||||||
* page (Gmail/Apple-Mail's native button calls this).
|
* page (Gmail/Apple-Mail's native button calls this).
|
||||||
*/
|
*/
|
||||||
app.post('/track/unsubscribe/:token', async (c) => {
|
app.post('/unsubscribe/:token', async (c) => {
|
||||||
const token = c.req.param('token');
|
const token = c.req.param('token');
|
||||||
const payload = verifyToken(token, trackingSecret);
|
const payload = verifyToken(token, trackingSecret);
|
||||||
if (!payload) return c.text('', 400);
|
if (!payload) return c.text('', 400);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue