@@ -13,7 +13,12 @@ const PORT = process.env.PORT || 3000
1313
1414const NOTION_FUNDRAISER_DATABASE_ID = "11ee959b7fdb4204a9ce46c9224b1818" ;
1515const NOTION_STORE_DATABASE_ID = "7c36ef34cebb48e097706442634abaaf" ;
16+ const redis = require ( 'redis' ) ;
17+ const client = redis . createClient ( ) ; // Connects to default 127.0.0.1:6379 on your Droplet
1618
19+ client . on ( 'error' , ( err ) => console . log ( 'Redis Client Error' , err ) ) ;
20+ client . connect ( ) ; // v4 of redis requires explicit connect
21+ const CACHE_TTL = 3500 ; // 60 minutes in seconds
1722console . log ( "starting up" )
1823app . use ( express . static ( "public" ) )
1924app . use ( bodyParser . json ( ) ) ;
@@ -178,16 +183,21 @@ app.get("/sex-ed/:slug", async (req,res) => {
178183// res.render("networked-performance/session")
179184// })
180185
181- app . get ( "/classes" , async ( req , res ) => {
182- const response = await getDatabaseEntries ( "6d5585af2f544dd1bad9d24c5e177026" , [ { property :"Date" , direction :"descending" } ] )
183- const projectData = response . map ( ( project ) => {
184- console . log ( project )
185- return parseClassData ( project )
186- } )
187- console . log ( projectData )
188- // let pageContent = getPageContent()
189- res . render ( "programs/classes" , { projects : projectData } )
190- } )
186+ app . get ( "/classes" , async ( req , res ) => {
187+ try {
188+ const classes = await getCachedData ( "notion:classes:list" , async ( ) => {
189+ const response = await getDatabaseEntries ( "6d5585af2f544dd1bad9d24c5e177026" , [
190+ { property : "Date" , direction : "descending" }
191+ ] ) ;
192+ return response . map ( project => parseClassData ( project ) ) ;
193+ } ) ;
194+
195+ res . render ( "programs/classes" , { projects :classes } ) ;
196+ } catch ( error ) {
197+ console . error ( "Classes Error:" , error ) ;
198+ res . status ( 500 ) . send ( "Error loading classes" ) ;
199+ }
200+ } ) ;
191201
192202
193203app . get ( "/sessions" , async ( req , res ) => {
@@ -1002,36 +1012,72 @@ app.get("/sessions/:slug", async (req, res) => {
10021012
10031013
10041014app . get ( "/sessions/:session/:class" , async ( req , res ) => {
1005- const data = await getDatabaseEntry ( "6d5585af2f544dd1bad9d24c5e177026" , { "and" :[ { property :"Website-Slug" , "rich_text" : { "equals" :req . params . class } } , { property :"Session Slug" , "rollup" : { "any" : { "rich_text" : { "equals" : req . params . session } } } } ] } )
1006- // const data = await getDatabaseEntry("57406c3b209e4bfba3953de6328086ac", {"and":[{property:"Website-Slug", "rich_text": {"equals":req.params.class}}, {property:"Session Slug", "rollup": { "any": { "rich_text": { "equals": req.params.session } }}}] })
1007- if ( ! data ) return
1008- const response = await prepareClassData ( data , req . params . class )
1009- res . render ( "programs/class-concurrent" , response ) ;
1015+ const { session, class : className } = req . params ;
1016+ const cacheKey = `cache:session:${ session } :class:${ className } ` ;
10101017
1011- } )
1018+ try {
1019+ // 1. Try to get data from Redis
1020+ const cachedData = await client . get ( cacheKey ) ;
10121021
1022+ if ( cachedData ) {
1023+ // 2. Serve from cache immediately
1024+ const parsedCache = JSON . parse ( cachedData ) ;
1025+ res . render ( "programs/class-concurrent" , parsedCache ) ;
1026+
1027+ // 3. Background Revalidation: Update cache without making user wait
1028+ revalidateClassData ( session , className , cacheKey ) . catch ( console . error ) ;
1029+ } else {
1030+ // 4. Cache Miss: Fetch from Notion (Only happens once per TTL)
1031+ const freshData = await revalidateClassData ( session , className , cacheKey ) ;
1032+ if ( freshData ) {
1033+ res . render ( "programs/class-concurrent" , freshData ) ;
1034+ } else {
1035+ res . status ( 404 ) . send ( "Class not found" ) ;
1036+ }
1037+ }
1038+ } catch ( error ) {
1039+ console . error ( "Route Error:" , error ) ;
1040+ res . status ( 500 ) . send ( "Internal Server Error" ) ;
1041+ }
10131042
1014- app . get ( "/projects" , async ( req , res ) => {
1015- const response = await getDatabaseEntries ( "713f24806a524c5e892971e4fbf5c9dd" , [ { property :"Release Date" , direction :"descending" } ] )
1016- const projectData = response . map ( ( project ) => {
1017- console . log ( project )
1018- return parseNotionPage ( project )
1019- } )
1020- console . log ( projectData )
1021- res . render ( "projects/projectList" , { projects : projectData } )
10221043} )
10231044
1024- app . get ( "/projects/:slug" , async ( req , res ) => {
1025- //filter by slug here
1026- console . log ( req . params . slug )
1027- const response = await getDatabaseEntry ( "713f24806a524c5e892971e4fbf5c9dd" , { property :"Website-Slug" , "rich_text" : { "equals" :req . params . slug } } )
1028- console . log ( response )
1029- if ( response ) {
1030- const projectData = parseProjectData ( response )
1031- console . log ( projectData )
1032- res . render ( "projects/projectPage" , projectData )
1045+
1046+ app . get ( "/projects" , async ( req , res ) => {
1047+ try {
1048+ const projects = await getCachedData ( "notion:projects:list" , async ( ) => {
1049+ const response = await getDatabaseEntries ( "713f24806a524c5e892971e4fbf5c9dd" , [
1050+ { property : "Release Date" , direction : "descending" }
1051+ ] ) ;
1052+ return response . map ( project => parseNotionPage ( project ) ) ;
1053+ } ) ;
1054+
1055+ res . render ( "projects/projectList" , { projects } ) ;
1056+ } catch ( error ) {
1057+ res . status ( 500 ) . send ( "Error" ) ;
10331058 }
1034- } )
1059+ } ) ;
1060+
1061+ app . get ( "/projects/:slug" , async ( req , res ) => {
1062+ const { slug } = req . params ;
1063+ try {
1064+ const projectData = await getCachedData ( `notion:project:${ slug } ` , async ( ) => {
1065+ const response = await getDatabaseEntry ( "713f24806a524c5e892971e4fbf5c9dd" , {
1066+ property : "Website-Slug" ,
1067+ rich_text : { equals : slug }
1068+ } ) ;
1069+ return response ? parseProjectData ( response ) : null ;
1070+ } ) ;
1071+
1072+ if ( projectData ) {
1073+ res . render ( "projects/projectPage" , projectData ) ;
1074+ } else {
1075+ res . status ( 404 ) . send ( "Project not found" ) ;
1076+ }
1077+ } catch ( error ) {
1078+ res . status ( 500 ) . send ( "Error" ) ;
1079+ }
1080+ } ) ;
10351081
10361082app . get ( "/yearbook" , async ( req , res ) => {
10371083 // const response = await getDatabaseEntries("ea99608272e446cd880cbcb8d2ee1e13", [{timestamp:"created_time", direction:"descending"}], {
@@ -1070,38 +1116,62 @@ app.get("/yearbook/:session", async (req,res) => {
10701116} )
10711117
10721118
1073- app . get ( "/blog" , async ( req , res ) => {
1074- const response = await getDatabaseEntries ( "5fb49fe53804424a89230294206fcaee" , [ { property : "Publish-Date" , direction : "descending" } ] )
1075- const blogData = response . map ( ( blog ) => {
1076- console . log ( blog )
1077- return parseBlog ( blog )
1078- } )
1079- console . log ( blogData )
1080- res . render ( "blog/blog" , { blog : blogData } )
1119+ app . get ( "/blog" , async ( req , res ) => {
1120+ try {
1121+ const blog = await getCachedData ( "cache: blog:list" , async ( ) => {
1122+ const response = await getDatabaseEntries ( "5fb49fe53804424a89230294206fcaee" , [
1123+ { property : "Publish-Date" , direction : "descending" }
1124+ ] ) ;
1125+ return response . map ( post => parseBlog ( post ) ) ;
1126+ } ) ;
10811127
1082- } )
1128+ res . render ( "blog/blog" , { blog } ) ;
1129+ } catch ( error ) {
1130+ console . error ( error ) ;
1131+ res . status ( 500 ) . send ( "Error loading blog" ) ;
1132+ }
1133+ } ) ;
10831134
10841135
10851136
10861137
10871138
1139+ app . get ( "/blog/:slug" , async ( req , res ) => {
1140+ const { slug } = req . params ;
1141+ const cacheKey = `cache:blog:${ slug } ` ;
10881142
1143+ try {
1144+ const cachedData = await client . get ( cacheKey ) ;
10891145
1090- app . get ( "/blog/:slug" , async ( req , res ) => {
1091- const response = await getDatabaseEntry ( "5fb49fe53804424a89230294206fcaee" , { property :"Website-Slug" , "rich_text" : { "equals" :req . params . slug } } )
1092- const parsedData = parseNotionPage ( response )
1146+ if ( cachedData ) {
1147+ // 1. Serve immediately from Redis
1148+ const parsedCache = JSON . parse ( cachedData ) ;
1149+ res . render ( "blog/post" , {
1150+ title : parsedCache . Name ,
1151+ postHTML : parsedCache . postHTML ,
1152+ ...parsedCache
1153+ } ) ;
10931154
1094- // const blogData = response.map((blog) => {
1095- // console.log(blog)
1096- // return parseNotionPage(blog)
1097- // })
1098-
1099- console . log ( parsedData )
1100- const pageContent = await getBlocks ( response . id )
1101- let postHTML = parsePageContentHTML ( pageContent )
1102- console . log ( postHTML )
1103- res . render ( "blog/post" , { title : parsedData . Name , postHTML :postHTML , ...parsedData } )
1104- } )
1155+ // 2. Update the cache in the background for the next person
1156+ revalidateBlogData ( slug , cacheKey ) . catch ( console . error ) ;
1157+ } else {
1158+ // 3. Cache Miss: Fetch and wait
1159+ const freshData = await revalidateBlogData ( slug , cacheKey ) ;
1160+ if ( freshData ) {
1161+ res . render ( "blog/post" , {
1162+ title : freshData . Name ,
1163+ postHTML : freshData . postHTML ,
1164+ ...freshData
1165+ } ) ;
1166+ } else {
1167+ res . status ( 404 ) . send ( "Post not found" ) ;
1168+ }
1169+ }
1170+ } catch ( error ) {
1171+ console . error ( "Blog Route Error:" , error ) ;
1172+ res . status ( 500 ) . send ( "Internal Server Error" ) ;
1173+ }
1174+ } ) ;
11051175
11061176
11071177// app.get("/ecpc", async (req,res) => {
@@ -2349,3 +2419,66 @@ function formatRichText(textArray) {
23492419 }
23502420 return formattedText
23512421}
2422+
2423+ async function getCachedData ( key , fetcher ) {
2424+ const cached = await client . get ( key ) ;
2425+
2426+ if ( cached ) {
2427+ // Optional: Trigger background update if stale (Advanced ISR)
2428+ return JSON . parse ( cached ) ;
2429+ }
2430+
2431+ // Cache Miss: Fetch fresh data
2432+ const freshData = await fetcher ( ) ;
2433+
2434+ // Store in Redis
2435+ await client . set ( key , JSON . stringify ( freshData ) , {
2436+ EX : CACHE_TTL
2437+ } ) ;
2438+
2439+ return freshData ;
2440+ }
2441+ async function revalidateClassData ( sessionSlug , classSlug , cacheKey ) {
2442+ // Your original query logic from index.js
2443+ const data = await getDatabaseEntry ( "6d5585af2f544dd1bad9d24c5e177026" , {
2444+ "and" : [
2445+ { property : "Website-Slug" , "rich_text" : { "equals" : classSlug } } ,
2446+ { property : "Session Slug" , "rollup" : { "any" : { "rich_text" : { "equals" : sessionSlug } } } }
2447+ ]
2448+ } ) ;
2449+
2450+ if ( ! data ) return null ;
2451+
2452+ // Your original processing logic
2453+ const response = await prepareClassData ( data , classSlug ) ;
2454+
2455+ // Store in Redis (Expire after 1 hour to account for image URL expiration)
2456+ await client . set ( cacheKey , JSON . stringify ( response ) , {
2457+ EX : 3500
2458+ } ) ;
2459+
2460+ return response ;
2461+ }
2462+ async function revalidateBlogData ( slug , cacheKey ) {
2463+ const response = await getDatabaseEntry ( "5fb49fe53804424a89230294206fcaee" , {
2464+ property : "Website-Slug" ,
2465+ rich_text : { "equals" : slug }
2466+ } ) ;
2467+
2468+ if ( ! response ) return null ;
2469+
2470+ const parsedData = parseNotionPage ( response ) ;
2471+ const pageContent = await getBlocks ( response . id ) ;
2472+
2473+ // Note: If you implement image downloading, make sure this is awaited
2474+ const postHTML = parsePageContentHTML ( pageContent ) ;
2475+
2476+ const finalData = { ...parsedData , postHTML } ;
2477+
2478+ // Update Redis with a TTL (e.g., 1 hour)
2479+ await client . set ( cacheKey , JSON . stringify ( finalData ) , {
2480+ EX : 3500
2481+ } ) ;
2482+
2483+ return finalData ;
2484+ }
0 commit comments