@@ -306,7 +306,7 @@ export const websitesRouter = createTRPCRouter({
306306 update : protectedProcedure
307307 . input ( updateWebsiteSchema )
308308 . mutation ( async ( { ctx, input } ) => {
309- // Authorize access before transaction
309+ // Authorize access and get billing info before transaction
310310 const websiteToUpdate = await authorizeWebsiteAccess (
311311 ctx ,
312312 input . id ,
@@ -349,6 +349,78 @@ export const websitesRouter = createTRPCRouter({
349349 websiteCache . invalidateByKey ( `getById:${ input . id } ` ) ,
350350 ] ) ;
351351
352+ // If isPublic status changed, invalidate all related caches
353+ if (
354+ input . isPublic !== undefined &&
355+ input . isPublic !== websiteToUpdate . isPublic
356+ ) {
357+ // Call the invalidateCaches procedure logic directly
358+ await Promise . all ( [
359+ // Website caches
360+ websiteCache . invalidateByTables ( [ 'websites' ] ) ,
361+ websiteCache . invalidateByKey ( `getById:${ input . id } ` ) ,
362+
363+ createDrizzleCache ( {
364+ redis,
365+ namespace : 'website_by_id' ,
366+ } ) . invalidateByKey ( `website_by_id:${ input . id } ` ) ,
367+ createDrizzleCache ( { redis, namespace : 'auth' } ) . invalidateByKey (
368+ `auth:${ ctx . user . id } :${ input . id } `
369+ ) ,
370+
371+ // Funnel caches
372+ createDrizzleCache ( {
373+ redis,
374+ namespace : 'funnels' ,
375+ } ) . invalidateByTables ( [ 'funnelDefinitions' ] ) ,
376+ createDrizzleCache ( { redis, namespace : 'funnels' } ) . invalidateByKey (
377+ `funnels:list:${ input . id } `
378+ ) ,
379+ createDrizzleCache ( { redis, namespace : 'funnels' } ) . invalidateByKey (
380+ `funnels:listPublic:${ input . id } `
381+ ) ,
382+
383+ // Goals caches
384+ createDrizzleCache ( { redis, namespace : 'goals' } ) . invalidateByTables ( [
385+ 'goals' ,
386+ ] ) ,
387+ createDrizzleCache ( { redis, namespace : 'goals' } ) . invalidateByKey (
388+ `goals:list:${ input . id } `
389+ ) ,
390+
391+ // Autocomplete caches
392+ createDrizzleCache ( {
393+ redis,
394+ namespace : 'autocomplete' ,
395+ } ) . invalidateByTables ( [ 'websites' ] ) ,
396+
397+ // Mini-charts caches
398+ createDrizzleCache ( {
399+ redis,
400+ namespace : 'mini-charts' ,
401+ } ) . invalidateByTables ( [ 'websites' ] ) ,
402+ createDrizzleCache ( {
403+ redis,
404+ namespace : 'mini-charts' ,
405+ } ) . invalidateByKey ( `mini-charts:${ ctx . user . id } :${ input . id } ` ) ,
406+ createDrizzleCache ( {
407+ redis,
408+ namespace : 'mini-charts' ,
409+ } ) . invalidateByKey ( `mini-charts:public:${ input . id } ` ) ,
410+ ] ) ;
411+
412+ logger . info (
413+ 'Public status changed - caches invalidated' ,
414+ `Website ${ input . id } public status changed to ${ input . isPublic } ` ,
415+ {
416+ websiteId : input . id ,
417+ oldIsPublic : websiteToUpdate . isPublic ,
418+ newIsPublic : input . isPublic ,
419+ userId : ctx . user . id ,
420+ }
421+ ) ;
422+ }
423+
352424 return updatedWebsite ;
353425 } ) ,
354426
@@ -420,22 +492,18 @@ export const websitesRouter = createTRPCRouter({
420492 const transferredWebsite = await ctx . db . transaction ( async ( tx ) => {
421493 const [ website ] = await tx
422494 . update ( websites )
423- . set ( { organizationId : input . organizationId ?? null } )
495+ . set ( {
496+ organizationId : input . organizationId ?? null ,
497+ updatedAt : new Date ( ) . toISOString ( ) ,
498+ } )
424499 . where ( eq ( websites . id , input . websiteId ) )
425500 . returning ( ) ;
426501
427- if ( ! website ) {
428- throw new TRPCError ( {
429- code : 'NOT_FOUND' ,
430- message : 'Website not found' ,
431- } ) ;
432- }
433-
434502 return website ;
435503 } ) ;
436504
437505 // Log success after transaction
438- logger . success (
506+ logger . info (
439507 'Website Transferred' ,
440508 `Website "${ transferredWebsite . name } " was transferred to organization "${ input . organizationId } "` ,
441509 {
@@ -454,6 +522,94 @@ export const websitesRouter = createTRPCRouter({
454522 return transferredWebsite ;
455523 } ) ,
456524
525+ invalidateCaches : protectedProcedure
526+ . input ( z . object ( { websiteId : z . string ( ) } ) )
527+ . mutation ( async ( { ctx, input } ) => {
528+ // Authorize access
529+ await authorizeWebsiteAccess ( ctx , input . websiteId , 'update' ) ;
530+
531+ try {
532+ // Invalidate all caches related to this website
533+ await Promise . all ( [
534+ // Website caches
535+ websiteCache . invalidateByTables ( [ 'websites' ] ) ,
536+ websiteCache . invalidateByKey ( `getById:${ input . websiteId } ` ) ,
537+
538+ createDrizzleCache ( {
539+ redis,
540+ namespace : 'website_by_id' ,
541+ } ) . invalidateByKey ( `website_by_id:${ input . websiteId } ` ) ,
542+ createDrizzleCache ( { redis, namespace : 'auth' } ) . invalidateByKey (
543+ `auth:${ ctx . user . id } :${ input . websiteId } `
544+ ) ,
545+
546+ // Funnel caches
547+ createDrizzleCache ( {
548+ redis,
549+ namespace : 'funnels' ,
550+ } ) . invalidateByTables ( [ 'funnelDefinitions' ] ) ,
551+ createDrizzleCache ( { redis, namespace : 'funnels' } ) . invalidateByKey (
552+ `funnels:list:${ input . websiteId } `
553+ ) ,
554+ createDrizzleCache ( { redis, namespace : 'funnels' } ) . invalidateByKey (
555+ `funnels:listPublic:${ input . websiteId } `
556+ ) ,
557+
558+ // Goals caches
559+ createDrizzleCache ( { redis, namespace : 'goals' } ) . invalidateByTables ( [
560+ 'goals' ,
561+ ] ) ,
562+ createDrizzleCache ( { redis, namespace : 'goals' } ) . invalidateByKey (
563+ `goals:list:${ input . websiteId } `
564+ ) ,
565+
566+ // Autocomplete caches
567+ createDrizzleCache ( {
568+ redis,
569+ namespace : 'autocomplete' ,
570+ } ) . invalidateByTables ( [ 'websites' ] ) ,
571+
572+ // Mini-charts caches
573+ createDrizzleCache ( {
574+ redis,
575+ namespace : 'mini-charts' ,
576+ } ) . invalidateByTables ( [ 'websites' ] ) ,
577+ createDrizzleCache ( {
578+ redis,
579+ namespace : 'mini-charts' ,
580+ } ) . invalidateByKey ( `mini-charts:${ ctx . user . id } :${ input . websiteId } ` ) ,
581+ createDrizzleCache ( {
582+ redis,
583+ namespace : 'mini-charts' ,
584+ } ) . invalidateByKey ( `mini-charts:public:${ input . websiteId } ` ) ,
585+ ] ) ;
586+
587+ logger . info (
588+ 'Caches invalidated' ,
589+ `All caches invalidated for website ${ input . websiteId } ` ,
590+ {
591+ websiteId : input . websiteId ,
592+ userId : ctx . user . id ,
593+ }
594+ ) ;
595+
596+ return { success : true } ;
597+ } catch ( error ) {
598+ logger . error (
599+ 'Failed to invalidate caches' ,
600+ error instanceof Error ? error . message : String ( error ) ,
601+ {
602+ websiteId : input . websiteId ,
603+ userId : ctx . user . id ,
604+ }
605+ ) ;
606+ throw new TRPCError ( {
607+ code : 'INTERNAL_SERVER_ERROR' ,
608+ message : 'Failed to invalidate caches' ,
609+ } ) ;
610+ }
611+ } ) ,
612+
457613 isTrackingSetup : publicProcedure
458614 . input ( z . object ( { websiteId : z . string ( ) } ) )
459615 . query ( async ( { ctx, input } ) => {
0 commit comments