@@ -59,18 +59,138 @@ async function startServer() {
5959 // Root endpoint
6060 app . get ( '/' , ( req , res ) => {
6161 res . json ( {
62- name : 'StackMemory Storage Test Server' ,
63- version : '1.0.0' ,
62+ name : 'StackMemory Core API' ,
63+ version : '0.3.17' ,
64+ description : 'Core API with Redis, PostgreSQL, and Railway Buckets' ,
6465 endpoints : [
6566 '/health' ,
66- '/api/health' ,
67+ '/api/health' ,
68+ '/api/login' ,
69+ '/api/status' ,
6770 '/test-storage' ,
6871 '/create-frame' ,
69- '/list-frames'
70- ]
72+ '/list-frames' ,
73+ '/api/frames'
74+ ] ,
75+ storage_tiers : {
76+ hot : 'Redis (< 24 hours)' ,
77+ warm : 'Railway Buckets (1-30 days)' ,
78+ cold : 'PostgreSQL (30+ days)'
79+ }
7180 } ) ;
7281 } ) ;
7382
83+ // Auto-login endpoint for seamless API access
84+ app . post ( '/api/login' , async ( req , res ) => {
85+ try {
86+ const { username, api_key } = req . body ;
87+
88+ // Simple API key validation (in production, use proper JWT/OAuth)
89+ const validApiKey = process . env . API_KEY_SECRET || 'development-secret' ;
90+
91+ if ( api_key === validApiKey ) {
92+ // Store session in Redis if available
93+ const sessionId = `session_${ Date . now ( ) } _${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) } ` ;
94+ const sessionData = {
95+ username : username || 'api_user' ,
96+ created_at : new Date ( ) . toISOString ( ) ,
97+ api_key_hash : 'valid' ,
98+ permissions : [ 'read' , 'write' , 'admin' ]
99+ } ;
100+
101+ if ( config . redisUrl ) {
102+ const redisClient = createClient ( { url : config . redisUrl } ) ;
103+ await redisClient . connect ( ) ;
104+ await redisClient . setEx ( `session:${ sessionId } ` , 3600 , JSON . stringify ( sessionData ) ) ; // 1 hour
105+ await redisClient . disconnect ( ) ;
106+ }
107+
108+ res . json ( {
109+ success : true ,
110+ session_id : sessionId ,
111+ message : 'Automatically logged into StackMemory Core API' ,
112+ access : {
113+ redis : config . redisUrl ? 'available' : 'not_configured' ,
114+ postgresql : config . databaseUrl ? 'available' : 'not_configured' ,
115+ buckets : 'configurable'
116+ }
117+ } ) ;
118+ } else {
119+ res . status ( 401 ) . json ( {
120+ success : false ,
121+ message : 'Invalid API key'
122+ } ) ;
123+ }
124+ } catch ( error : any ) {
125+ res . status ( 500 ) . json ( {
126+ success : false ,
127+ message : 'Login failed' ,
128+ error : error . message
129+ } ) ;
130+ }
131+ } ) ;
132+
133+ // Core API status endpoint
134+ app . get ( '/api/status' , async ( req , res ) => {
135+ const status : any = {
136+ service : 'stackmemory-core-api' ,
137+ version : '0.3.17' ,
138+ environment : process . env . NODE_ENV || 'production' ,
139+ timestamp : new Date ( ) . toISOString ( ) ,
140+ storage : {
141+ tiers : { }
142+ }
143+ } ;
144+
145+ // Check Redis (Hot Tier)
146+ if ( config . redisUrl ) {
147+ try {
148+ const redisClient = createClient ( { url : config . redisUrl } ) ;
149+ await redisClient . connect ( ) ;
150+
151+ const info = await redisClient . info ( 'memory' ) ;
152+ const keys = await redisClient . keys ( '*' ) ;
153+ const usedMemory = info . match ( / u s e d _ m e m o r y _ h u m a n : ( .+ ) / ) ?. [ 1 ] ;
154+
155+ status . storage . tiers . hot_redis = {
156+ status : 'connected' ,
157+ memory_used : usedMemory ?. trim ( ) ,
158+ keys : keys . length ,
159+ ttl_policy : 'auto-expire'
160+ } ;
161+
162+ await redisClient . disconnect ( ) ;
163+ } catch ( error : any ) {
164+ status . storage . tiers . hot_redis = { status : 'error' , message : error . message } ;
165+ }
166+ } else {
167+ status . storage . tiers . hot_redis = { status : 'not_configured' } ;
168+ }
169+
170+ // Check PostgreSQL (Cold Tier)
171+ if ( config . databaseUrl ) {
172+ try {
173+ const pgClient = new Client ( { connectionString : config . databaseUrl } ) ;
174+ await pgClient . connect ( ) ;
175+
176+ const result = await pgClient . query ( 'SELECT COUNT(*) as frames FROM frames WHERE created_at > NOW() - INTERVAL \'24 hours\'' ) ;
177+ const totalFrames = await pgClient . query ( 'SELECT COUNT(*) as total FROM frames' ) ;
178+
179+ status . storage . tiers . cold_postgresql = {
180+ status : 'connected' ,
181+ recent_frames : parseInt ( result . rows [ 0 ] . frames ) ,
182+ total_frames : parseInt ( totalFrames . rows [ 0 ] . total )
183+ } ;
184+
185+ await pgClient . end ( ) ;
186+ } catch ( error : any ) {
187+ status . storage . tiers . cold_postgresql = { status : 'error' , message : error . message } ;
188+ }
189+ }
190+
191+ res . json ( status ) ;
192+ } ) ;
193+
74194 // Comprehensive storage test endpoint
75195 app . get ( '/test-storage' , async ( req , res ) => {
76196 const results : StorageTestResult = {
@@ -316,7 +436,88 @@ async function startServer() {
316436 }
317437 } ) ;
318438
319- // List recent frames
439+ // Core API frames endpoint
440+ app . get ( '/api/frames' , async ( req , res ) => {
441+ try {
442+ const limit = parseInt ( req . query . limit as string ) || 10 ;
443+ const source = req . query . source as string || 'all' ; // 'redis', 'postgresql', 'all'
444+
445+ const results : any = {
446+ frames : [ ] ,
447+ sources_checked : [ ] ,
448+ total_count : 0
449+ } ;
450+
451+ // From Redis (hot tier) - most recent
452+ if ( ( source === 'all' || source === 'redis' ) && config . redisUrl ) {
453+ try {
454+ const redisClient = createClient ( { url : config . redisUrl } ) ;
455+ await redisClient . connect ( ) ;
456+ const redisKeys = await redisClient . keys ( 'frame:*' ) ;
457+ const redisFrames = [ ] ;
458+ for ( const key of redisKeys . slice ( 0 , limit ) ) {
459+ const data = await redisClient . get ( key ) ;
460+ if ( data ) {
461+ const frame = JSON . parse ( data ) ;
462+ frame . source = 'redis' ;
463+ frame . tier = 'hot' ;
464+ redisFrames . push ( frame ) ;
465+ }
466+ }
467+ await redisClient . disconnect ( ) ;
468+ results . frames . push ( ...redisFrames ) ;
469+ results . sources_checked . push ( 'redis' ) ;
470+ } catch ( error : any ) {
471+ results . redis_error = error . message ;
472+ }
473+ }
474+
475+ // From PostgreSQL (cold tier) - persistent storage
476+ if ( ( source === 'all' || source === 'postgresql' ) && config . databaseUrl ) {
477+ try {
478+ const pgClient = new Client ( { connectionString : config . databaseUrl } ) ;
479+ await pgClient . connect ( ) ;
480+ const pgFrames = await pgClient . query (
481+ 'SELECT *, \'postgresql\' as source, \'cold\' as tier FROM frames ORDER BY created_at DESC LIMIT $1' ,
482+ [ limit ]
483+ ) ;
484+ await pgClient . end ( ) ;
485+ results . frames . push ( ...pgFrames . rows ) ;
486+ results . sources_checked . push ( 'postgresql' ) ;
487+ results . total_count = pgFrames . rowCount ;
488+ } catch ( error : any ) {
489+ results . postgresql_error = error . message ;
490+ }
491+ }
492+
493+ // Sort by created_at if multiple sources
494+ if ( results . frames . length > 1 ) {
495+ results . frames . sort ( ( a : any , b : any ) =>
496+ new Date ( b . created_at ) . getTime ( ) - new Date ( a . created_at ) . getTime ( )
497+ ) ;
498+ }
499+
500+ res . json ( {
501+ count : results . frames . length ,
502+ frames : results . frames . slice ( 0 , limit ) ,
503+ metadata : {
504+ sources_checked : results . sources_checked ,
505+ total_in_postgresql : results . total_count ,
506+ tier_explanation : {
507+ hot : 'Redis - Recent frames (< 24 hours)' ,
508+ cold : 'PostgreSQL - All frames (persistent storage)'
509+ }
510+ }
511+ } ) ;
512+ } catch ( error : any ) {
513+ res . status ( 500 ) . json ( {
514+ error : 'Failed to fetch frames' ,
515+ message : error . message
516+ } ) ;
517+ }
518+ } ) ;
519+
520+ // List recent frames (legacy endpoint)
320521 app . get ( '/list-frames' , async ( req , res ) => {
321522 const results : any = { sources : { } } ;
322523
0 commit comments