1- import { createHash } from 'crypto'
2-
31import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events'
42import db from '@codebuff/internal/db'
53import * as schema from '@codebuff/internal/db/schema'
@@ -9,7 +7,6 @@ import { z } from 'zod'
97
108import { requireUserFromApiKey } from '../../_helpers'
119
12- import type { processAndGrantCredit as ProcessAndGrantCreditFn } from '@codebuff/billing/grant-credits'
1310import type { TrackEventFn } from '@codebuff/common/types/contracts/analytics'
1411import type { GetUserInfoFromApiKeyFn } from '@codebuff/common/types/contracts/database'
1512import type {
@@ -18,10 +15,6 @@ import type {
1815} from '@codebuff/common/types/contracts/logger'
1916import type { NextRequest } from 'next/server'
2017
21- // Revenue share: users get 75% of payout as credits
22- const AD_REVENUE_SHARE = 0.75
23- const MINIMUM_CREDITS_GRANTED = 2
24-
2518// Rate limiting: max impressions per user per hour
2619const MAX_IMPRESSIONS_PER_HOUR = 60
2720
@@ -78,22 +71,8 @@ function checkRateLimit(userId: string): boolean {
7871 return true
7972}
8073
81- /**
82- * Generate a deterministic operation ID for deduplication.
83- * Same user + same impUrl = same operationId, preventing duplicate credits.
84- */
85- function generateImpressionOperationId ( userId : string , impUrl : string ) : string {
86- const hash = createHash ( 'sha256' )
87- . update ( `${ userId } :${ impUrl } ` )
88- . digest ( 'hex' )
89- . slice ( 0 , 16 )
90- return `ad-imp-${ hash } `
91- }
92-
9374const bodySchema = z . object ( {
94- // Only impUrl needed - we look up the ad data from our database
9575 impUrl : z . url ( ) ,
96- // Mode to determine if credits should be granted (FREE mode gets no credits)
9776 mode : z . string ( ) . optional ( ) ,
9877} )
9978
@@ -103,22 +82,19 @@ export async function postAdImpression(params: {
10382 logger : Logger
10483 loggerWithContext : LoggerWithContextFn
10584 trackEvent : TrackEventFn
106- processAndGrantCredit : typeof ProcessAndGrantCreditFn
10785 fetch : typeof globalThis . fetch
10886} ) {
10987 const {
11088 req,
11189 getUserInfoFromApiKey,
11290 loggerWithContext,
11391 trackEvent,
114- processAndGrantCredit,
11592 fetch,
11693 } = params
11794 const baseLogger = params . logger
11895
11996 // Parse and validate request body
12097 let impUrl : string
121- let mode : string | undefined
12298 try {
12399 const json = await req . json ( )
124100 const parsed = bodySchema . safeParse ( json )
@@ -129,7 +105,6 @@ export async function postAdImpression(params: {
129105 )
130106 }
131107 impUrl = parsed . data . impUrl
132- mode = parsed . data . mode
133108 } catch {
134109 return NextResponse . json (
135110 { error : 'Invalid JSON in request body' } ,
@@ -203,16 +178,10 @@ export async function postAdImpression(params: {
203178 )
204179 }
205180
206- // Get payout from the trusted database record
207- const payout = parseFloat ( adRecord . payout )
208-
209- // Generate deterministic operation ID for deduplication
210- const operationId = generateImpressionOperationId ( userId , impUrl )
211-
212181 // Fire the impression pixel to Gravity
213182 try {
214183 await fetch ( impUrl )
215- logger . info ( { userId, operationId , impUrl } , '[ads] Fired impression pixel' )
184+ logger . info ( { userId, impUrl } , '[ads] Fired impression pixel' )
216185 } catch ( error ) {
217186 logger . warn (
218187 {
@@ -224,82 +193,25 @@ export async function postAdImpression(params: {
224193 } ,
225194 '[ads] Failed to fire impression pixel' ,
226195 )
227- // Continue anyway - we still want to grant credits
196+ // Continue anyway - we still want to record the impression
228197 }
229198
230- // Calculate credits to grant (75% of payout, converted to credits)
231- // Payout is in dollars, credits are 1:1 with cents, so multiply by 100
232- const userShareDollars = payout * AD_REVENUE_SHARE
233- const creditsToGrant = Math . max (
234- MINIMUM_CREDITS_GRANTED + Math . floor ( 3 * Math . random ( ) ) ,
235- Math . floor ( userShareDollars * 100 ) ,
236- )
237-
238- let creditsGranted = 0
239- // FREE mode should not grant any credits
240- if ( mode !== 'FREE' && creditsToGrant > 0 ) {
241- try {
242- await processAndGrantCredit ( {
243- userId,
244- amount : creditsToGrant ,
245- type : 'ad' ,
246- description : `Ad impression credit (${ ( userShareDollars * 100 ) . toFixed ( 1 ) } ¢ from $${ payout . toFixed ( 4 ) } payout)` ,
247- expiresAt : null , // Ad credits don't expire
248- operationId,
249- logger,
250- } )
251-
252- creditsGranted = creditsToGrant
253-
254- logger . info (
255- {
256- userId,
257- payout,
258- creditsGranted,
259- operationId,
260- } ,
261- '[ads] Granted ad impression credits' ,
262- )
263-
264- trackEvent ( {
265- event : AnalyticsEvent . CREDIT_GRANT ,
266- userId,
267- properties : {
268- type : 'ad' ,
269- amount : creditsGranted ,
270- payout,
271- } ,
272- logger,
273- } )
274- } catch ( error ) {
275- logger . error (
276- {
277- userId,
278- payout,
279- error :
280- error instanceof Error
281- ? { name : error . name , message : error . message }
282- : error ,
283- } ,
284- '[ads] Failed to grant ad impression credits' ,
285- )
286- // Don't fail the request - we still want to update the impression record
287- }
288- }
199+ // No credits granted for ad impressions
200+ const creditsGranted = 0
289201
290202 // Update the ad_impression record with impression details (for ALL modes)
291203 try {
292204 await db
293205 . update ( schema . adImpression )
294206 . set ( {
295207 impression_fired_at : new Date ( ) ,
296- credits_granted : creditsGranted ,
297- grant_operation_id : creditsGranted > 0 ? operationId : null ,
208+ credits_granted : 0 ,
209+ grant_operation_id : null ,
298210 } )
299211 . where ( eq ( schema . adImpression . id , adRecord . id ) )
300212
301213 logger . info (
302- { userId, impUrl, creditsGranted , creditsToGrant } ,
214+ { userId, impUrl } ,
303215 '[ads] Updated ad impression record' ,
304216 )
305217 } catch ( error ) {
0 commit comments