(params: {
req: NextRequest
getUserInfoFromApiKey: GetUserInfoFromApiKeyFn
logger: Logger
loggerWithContext: LoggerWithContextFn
trackEvent: TrackEventFn
fetch: typeof globalThis.fetch
})
| 77 | }) |
| 78 | |
| 79 | export async function postAdImpression(params: { |
| 80 | req: NextRequest |
| 81 | getUserInfoFromApiKey: GetUserInfoFromApiKeyFn |
| 82 | logger: Logger |
| 83 | loggerWithContext: LoggerWithContextFn |
| 84 | trackEvent: TrackEventFn |
| 85 | fetch: typeof globalThis.fetch |
| 86 | }) { |
| 87 | const { req, getUserInfoFromApiKey, loggerWithContext, trackEvent, fetch } = |
| 88 | params |
| 89 | const baseLogger = params.logger |
| 90 | |
| 91 | // Parse and validate request body |
| 92 | let impUrl: string |
| 93 | try { |
| 94 | const json = await req.json() |
| 95 | const parsed = bodySchema.safeParse(json) |
| 96 | if (!parsed.success) { |
| 97 | return NextResponse.json( |
| 98 | { error: 'Invalid request body', details: parsed.error.format() }, |
| 99 | { status: 400 }, |
| 100 | ) |
| 101 | } |
| 102 | impUrl = parsed.data.impUrl |
| 103 | } catch { |
| 104 | return NextResponse.json( |
| 105 | { error: 'Invalid JSON in request body' }, |
| 106 | { status: 400 }, |
| 107 | ) |
| 108 | } |
| 109 | |
| 110 | const authed = await requireUserFromApiKey({ |
| 111 | req, |
| 112 | getUserInfoFromApiKey, |
| 113 | logger: baseLogger, |
| 114 | loggerWithContext, |
| 115 | trackEvent, |
| 116 | authErrorEvent: AnalyticsEvent.USAGE_API_AUTH_ERROR, |
| 117 | }) |
| 118 | if (!authed.ok) return authed.response |
| 119 | |
| 120 | const { userId, logger } = authed.data |
| 121 | |
| 122 | // Look up the ad from our database using the impUrl |
| 123 | // This ensures we use server-side trusted data, not client-provided data |
| 124 | const adRecord = await db.query.adImpression.findFirst({ |
| 125 | where: eq(schema.adImpression.imp_url, impUrl), |
| 126 | }) |
| 127 | |
| 128 | if (!adRecord) { |
| 129 | logger.warn( |
| 130 | { userId, impUrl }, |
| 131 | '[ads] Ad impression not found in database - was it served through our API?', |
| 132 | ) |
| 133 | return NextResponse.json( |
| 134 | { success: false, error: 'Ad not found', creditsGranted: 0 }, |
| 135 | { status: 404 }, |
| 136 | ) |
no test coverage detected