11import { NextResponse } from 'next/server' ;
22import { FetchPlugins } from '../v1/plugins/GetPlugins' ;
33import { GraphQLUpdates } from '../v2/GraphQLHandler' ;
4- import { GithubGraphQL } from '../v2/GraphQLInterop' ;
5- import { GetPluginMetadata } from '../v1/plugins/GetPluginMetadata' ;
4+ import { Database , firebaseAdmin } from '../Firebase' ;
65
76export const revalidate = 1800 ;
7+ const CACHE_DURATION_MS = 30 * 60 * 1000 ;
8+
9+ const createSafeCacheKey = ( owner : string , repo : string ) : string => {
10+ const combined = `${ owner } __${ repo } ` ;
11+ const sanitized = combined . replace ( / [ ^ a - z A - Z 0 - 9 _ - ] / g, '_' ) ;
12+ const withoutLeadingDot = sanitized . replace ( / ^ \. + / , '_' ) ;
13+
14+ const maxLength = 1500 ;
15+ const truncated = withoutLeadingDot . length > maxLength ? withoutLeadingDot . substring ( 0 , maxLength ) : withoutLeadingDot ;
16+
17+ const cleaned = truncated || 'unknown_repo' ;
18+ return cleaned . replace ( / _ _ + / g, '__' ) ;
19+ } ;
20+
21+ const isCacheValid = ( timestamp : FirebaseFirestore . Timestamp ) : boolean => {
22+ const now = new Date ( ) ;
23+ const cacheTime = timestamp . toDate ( ) ;
24+ return now . getTime ( ) - cacheTime . getTime ( ) < CACHE_DURATION_MS ;
25+ } ;
26+
27+ const getCachedRepositoryData = async ( owner : string , repo : string ) : Promise < CachedRepositoryData | null > => {
28+ try {
29+ const cacheKey = createSafeCacheKey ( owner , repo ) ;
30+ const docRef = Database . collection ( 'UpdateCache' ) . doc ( cacheKey ) ;
31+ const doc = await docRef . get ( ) ;
32+
33+ if ( ! doc . exists ) {
34+ return null ;
35+ }
36+
37+ const data = doc . data ( ) as UpdateCacheEntry ;
38+ if ( data && isCacheValid ( data . timestamp ) ) {
39+ console . log ( `Found valid cached data for ${ owner } /${ repo } (key: ${ cacheKey } )` ) ;
40+ return data . data ;
41+ } else if ( data ) {
42+ // Remove expired entry
43+ await docRef . delete ( ) ;
44+ console . log ( `Removed expired cache entry for ${ owner } /${ repo } (key: ${ cacheKey } )` ) ;
45+ }
46+
47+ return null ;
48+ } catch ( error ) {
49+ console . error ( `Error retrieving cached data for ${ owner } /${ repo } :` , error ) ;
50+ return null ;
51+ }
52+ } ;
53+
54+ const setCachedRepositoryData = async ( owner : string , repo : string , data : CachedRepositoryData ) : Promise < void > => {
55+ try {
56+ const cacheKey = createSafeCacheKey ( owner , repo ) ;
57+ const docRef = Database . collection ( 'UpdateCache' ) . doc ( cacheKey ) ;
58+ const now = new Date ( ) ;
59+
60+ const cacheEntry : UpdateCacheEntry = {
61+ owner,
62+ repo,
63+ data,
64+ timestamp : firebaseAdmin . firestore . Timestamp . fromDate ( now ) ,
65+ expiresAt : firebaseAdmin . firestore . Timestamp . fromDate ( new Date ( now . getTime ( ) + CACHE_DURATION_MS ) ) ,
66+ } ;
67+
68+ await docRef . set ( cacheEntry ) ;
69+ console . log ( `Cached repository data for ${ owner } /${ repo } (key: ${ cacheKey } ) for ${ CACHE_DURATION_MS / ( 1000 * 60 ) } minutes` ) ;
70+ } catch ( error ) {
71+ console . error ( `Error caching data for ${ owner } /${ repo } :` , error ) ;
72+ }
73+ } ;
74+
75+ const getCachedPluginData = async ( type : 'allPlugins' ) : Promise < any | null > => {
76+ try {
77+ const docRef = Database . collection ( 'PluginCache' ) . doc ( type ) ;
78+ const doc = await docRef . get ( ) ;
79+
80+ if ( ! doc . exists ) {
81+ return null ;
82+ }
83+
84+ const data = doc . data ( ) as PluginCacheEntry ;
85+ if ( data && isCacheValid ( data . timestamp ) ) {
86+ console . log ( `Found valid cached ${ type } data` ) ;
87+ return data . data ;
88+ } else if ( data ) {
89+ // Remove expired entry
90+ await docRef . delete ( ) ;
91+ console . log ( `Removed expired ${ type } cache entry` ) ;
92+ }
93+
94+ return null ;
95+ } catch ( error ) {
96+ console . error ( `Error retrieving cached ${ type } data:` , error ) ;
97+ return null ;
98+ }
99+ } ;
100+
101+ const setCachedPluginData = async ( type : 'allPlugins' , data : any ) : Promise < void > => {
102+ try {
103+ const docRef = Database . collection ( 'PluginCache' ) . doc ( type ) ;
104+ const now = new Date ( ) ;
105+
106+ const cacheEntry : PluginCacheEntry = {
107+ type,
108+ data,
109+ timestamp : firebaseAdmin . firestore . Timestamp . fromDate ( now ) ,
110+ expiresAt : firebaseAdmin . firestore . Timestamp . fromDate ( new Date ( now . getTime ( ) + CACHE_DURATION_MS ) ) ,
111+ } ;
112+
113+ await docRef . set ( cacheEntry ) ;
114+ console . log ( `Cached ${ type } data for ${ CACHE_DURATION_MS / ( 1000 * 60 ) } minutes` ) ;
115+ } catch ( error ) {
116+ console . error ( `Error caching ${ type } data:` , error ) ;
117+ }
118+ } ;
8119
9120interface PluginUpdateCheck {
10121 id : string ;
@@ -25,53 +136,130 @@ interface PluginUpdateStatus {
25136 pluginInfo : any ;
26137}
27138
28- async function CheckForThemeUpdates ( requestBody ) {
29- // Check if themes are provided
139+ interface CachedRepositoryData {
140+ download : string ;
141+ name : string ;
142+ commit : string ;
143+ url : string ;
144+ date : string ;
145+ message : string ;
146+ }
147+
148+ interface UpdateCacheEntry {
149+ owner : string ;
150+ repo : string ;
151+ data : CachedRepositoryData ;
152+ timestamp : FirebaseFirestore . Timestamp ;
153+ expiresAt : FirebaseFirestore . Timestamp ;
154+ }
155+
156+ interface ThemeUpdateRequest {
157+ owner : string ;
158+ repo : string ;
159+ }
160+
161+ interface PluginCacheEntry {
162+ type : 'allPlugins' ;
163+ data : any ;
164+ timestamp : FirebaseFirestore . Timestamp ;
165+ expiresAt : FirebaseFirestore . Timestamp ;
166+ }
167+
168+ async function CheckForThemeUpdates ( requestBody : ThemeUpdateRequest [ ] ) {
30169 if ( ! requestBody || requestBody . length === 0 ) {
31170 return [ ] ;
32171 }
33172
34- const graphQLHandler = new GraphQLUpdates ( ) ;
35- requestBody . forEach ( ( item ) => graphQLHandler . add ( item . owner , item . repo ) ) ;
36-
37- return new Promise ( async ( resolve ) => {
38- resolve (
39- Object . values ( ( await GithubGraphQL . Post ( graphQLHandler . get ( ) ) ) . data ) . map ( ( repository , i ) => ( {
40- download : `https://codeload.github.com/${ requestBody [ i ] ?. owner } /${ requestBody [ i ] ?. repo } /zip/refs/heads/${ repository ?. default_branch ?. name } ` ,
41- name : repository ?. name ?? null ,
42- commit : repository ?. defaultBranchRef ?. target ?. oid ?? null ,
43- url : repository ?. defaultBranchRef ?. target ?. commitUrl ?? null ,
44- date : repository ?. defaultBranchRef ?. target ?. committedDate ?? null ,
45- message : repository ?. defaultBranchRef ?. target ?. history ?. edges [ 0 ] ?. node . message ?? null ,
46- } ) ) ,
47- ) ;
173+ const results : CachedRepositoryData [ ] = [ ] ;
174+ const itemsToFetch : ThemeUpdateRequest [ ] = [ ] ;
175+ const cachePromises : Promise < void > [ ] = [ ] ;
176+
177+ for ( const item of requestBody ) {
178+ const cachedData = await getCachedRepositoryData ( item . owner , item . repo ) ;
179+
180+ if ( cachedData ) {
181+ results . push ( cachedData ) ;
182+ } else {
183+ itemsToFetch . push ( item ) ;
184+ }
185+ }
186+
187+ if ( itemsToFetch . length > 0 ) {
188+ const graphQLHandler = new GraphQLUpdates ( ) ;
189+ itemsToFetch . forEach ( ( item ) => graphQLHandler . add ( item . owner , item . repo ) ) ;
190+
191+ try {
192+ // Manual fetch to GitHub GraphQL API to avoid caching conflicts
193+ const response = await fetch ( 'https://api.github.com/graphql' , {
194+ method : 'POST' ,
195+ headers : {
196+ 'Content-Type' : 'application/json' ,
197+ ...( process . env . BEARER ? { Authorization : process . env . BEARER } : { } ) ,
198+ } ,
199+ body : JSON . stringify ( { query : graphQLHandler . get ( ) } ) ,
200+ } ) ;
201+
202+ const json = await response . json ( ) ;
203+ const fetchedData = Object . values ( json . data ) . map ( ( repository : any , i : number ) => {
204+ const item = itemsToFetch [ i ] ;
205+ const repositoryData : CachedRepositoryData = {
206+ download : `https://codeload.github.com/${ item ?. owner } /${ item ?. repo } /zip/refs/heads/${ repository ?. default_branch ?. name } ` ,
207+ name : repository ?. name ?? null ,
208+ commit : repository ?. defaultBranchRef ?. target ?. oid ?? null ,
209+ url : repository ?. defaultBranchRef ?. target ?. commitUrl ?? null ,
210+ date : repository ?. defaultBranchRef ?. target ?. committedDate ?? null ,
211+ message : repository ?. defaultBranchRef ?. target ?. history ?. edges [ 0 ] ?. node . message ?? null ,
212+ } ;
213+
214+ cachePromises . push ( setCachedRepositoryData ( item . owner , item . repo , repositoryData ) ) ;
215+
216+ return repositoryData ;
217+ } ) ;
218+
219+ results . push ( ...fetchedData ) ;
220+ } catch ( error ) {
221+ console . error ( 'Error fetching missing repositories from GitHub:' , error ) ;
222+ throw error ;
223+ }
224+ }
225+
226+ Promise . all ( cachePromises ) . catch ( ( error ) => {
227+ console . error ( 'Error updating cache entries:' , error ) ;
48228 } ) ;
229+
230+ const orderedResults : CachedRepositoryData [ ] = [ ] ;
231+ for ( const item of requestBody ) {
232+ const result = results . find ( ( r ) => r . name === item . repo ) ;
233+ if ( result ) {
234+ orderedResults . push ( result ) ;
235+ }
236+ }
237+
238+ return orderedResults ;
49239}
50240
51241async function CheckForPluginUpdates ( plugins : PluginUpdateCheck [ ] ) {
52- // Check if plugins are provided
53242 if ( ! plugins || plugins . length === 0 ) {
54243 return [ ] ;
55244 }
56245
57- // Try to get cached data
58- const cachedAllPlugins = global . requestCache . get ( ' allPlugins' ) ;
59- const cachedMetadata = global . requestCache . get ( 'pluginMetadata' ) ;
246+ let cachedData = await getCachedPluginData ( 'allPlugins' ) ;
247+ let allPlugins : any ;
248+ let metadata : any ;
60249
61- let allPlugins , metadata ;
250+ if ( ! cachedData ) {
251+ const fetchResult = await FetchPlugins ( ) ;
252+ allPlugins = fetchResult . pluginData ;
253+ metadata = fetchResult . metadata ;
62254
63- if ( cachedAllPlugins && cachedMetadata ) {
64- allPlugins = cachedAllPlugins ;
65- metadata = cachedMetadata ;
255+ setCachedPluginData ( 'allPlugins' , fetchResult ) . catch ( ( error ) => {
256+ console . error ( 'Error caching plugin data:' , error ) ;
257+ } ) ;
66258 } else {
67- [ allPlugins , metadata ] = await Promise . all ( [ FetchPlugins ( ) , GetPluginMetadata ( ) ] ) ;
68-
69- // Cache the results for 1 hour
70- global . requestCache . set ( 'allPlugins' , allPlugins , 3600 ) ;
71- global . requestCache . set ( 'pluginMetadata' , metadata , 3600 ) ;
259+ allPlugins = cachedData . pluginData ;
260+ metadata = cachedData . metadata ;
72261 }
73262
74- // Check update status for all plugins
75263 const pluginStatuses : PluginUpdateStatus [ ] = plugins . map ( ( plugin ) => {
76264 const metadataEntry = metadata . find ( ( m ) => m . id === plugin . id ) ;
77265 const pluginInfo = allPlugins . find ( ( p ) => p . initCommitId === plugin . id ) ;
0 commit comments