mirror of
https://github.com/velocitatem/PHANTOM.git
synced 2026-05-31 08:33:36 +00:00
tentative session storage with maybe using airtable
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
|
import { getSession, createSession } from '@/lib/sessionStore';
|
||||||
|
|
||||||
const COOKIE_NAME = 'phantom_session_id';
|
const COOKIE_NAME = 'phantom_session_id';
|
||||||
const isProd = process.env.NODE_ENV === 'production';
|
const isProd = process.env.NODE_ENV === 'production';
|
||||||
@@ -10,13 +11,18 @@ export async function GET(req: NextRequest) {
|
|||||||
const existingSession = req.cookies.get(COOKIE_NAME)?.value;
|
const existingSession = req.cookies.get(COOKIE_NAME)?.value;
|
||||||
|
|
||||||
if (existingSession) {
|
if (existingSession) {
|
||||||
return NextResponse.json({ sessionId: existingSession });
|
const sessionData = getSession(existingSession);
|
||||||
|
return NextResponse.json({
|
||||||
|
sessionId: existingSession,
|
||||||
|
experimentId: sessionData?.experimentId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// mint new session id
|
// mint new session id
|
||||||
const sessionId = randomUUID();
|
const sessionId = randomUUID();
|
||||||
|
createSession(sessionId);
|
||||||
|
|
||||||
const res = NextResponse.json({ sessionId });
|
const res = NextResponse.json({ sessionId, experimentId: undefined });
|
||||||
|
|
||||||
// set httpOnly cookie with security flags
|
// set httpOnly cookie with security flags
|
||||||
res.cookies.set({
|
res.cookies.set({
|
||||||
|
|||||||
38
web/src/hooks/useSession.ts
Normal file
38
web/src/hooks/useSession.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
type SessionState = {
|
||||||
|
sessionId: string | null;
|
||||||
|
experimentId: string | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSession = () => {
|
||||||
|
const [state, setState] = useState<SessionState>({
|
||||||
|
sessionId: null,
|
||||||
|
experimentId: null,
|
||||||
|
isLoading: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchSession = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/session');
|
||||||
|
if (!res.ok) throw new Error(`fetch failed: ${res.status}`);
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
setState({
|
||||||
|
sessionId: data.sessionId || null,
|
||||||
|
experimentId: data.experimentId || null,
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('session fetch error:', err);
|
||||||
|
setState({ sessionId: null, experimentId: null, isLoading: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchSession();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
};
|
||||||
102
web/src/lib/sessionStore.ts
Normal file
102
web/src/lib/sessionStore.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
type SessionData = {
|
||||||
|
experimentId?: string;
|
||||||
|
startedAt: number;
|
||||||
|
status: 'active' | 'stopped';
|
||||||
|
};
|
||||||
|
|
||||||
|
type ExperimentData = {
|
||||||
|
id: string;
|
||||||
|
status: 'active' | 'stopped';
|
||||||
|
sessionIds: string[];
|
||||||
|
createdAt: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = new Map<string, SessionData>();
|
||||||
|
const experiments = new Map<string, ExperimentData>();
|
||||||
|
|
||||||
|
const cfg = {
|
||||||
|
key: process.env.AIRTABLE_API_KEY,
|
||||||
|
base: process.env.AIRTABLE_BASE_ID,
|
||||||
|
table: process.env.AIRTABLE_TABLE_NAME || 'Sessions',
|
||||||
|
};
|
||||||
|
|
||||||
|
// sync session to airtable if credentials present
|
||||||
|
const syncToAirtable = async (sid: string, data: SessionData) => {
|
||||||
|
if (!cfg.key || !cfg.base) return; // skip if not configured
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = `https://api.airtable.com/v0/${cfg.base}/${encodeURIComponent(cfg.table)}`;
|
||||||
|
await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${cfg.key}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
fields: {
|
||||||
|
sessionId: sid,
|
||||||
|
experimentId: data.experimentId || '',
|
||||||
|
startedAt: new Date(data.startedAt).toISOString(),
|
||||||
|
status: data.status,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('airtable sync failed:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSession = (sid: string) => store.get(sid);
|
||||||
|
|
||||||
|
export const createSession = (sid: string) => {
|
||||||
|
const data: SessionData = { startedAt: Date.now(), status: 'active' };
|
||||||
|
store.set(sid, data);
|
||||||
|
syncToAirtable(sid, data); // async fire-and-forget
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setExperiment = (sid: string, expId: string) => {
|
||||||
|
const data = store.get(sid) || createSession(sid);
|
||||||
|
data.experimentId = expId;
|
||||||
|
store.set(sid, data);
|
||||||
|
syncToAirtable(sid, data);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stopExperiment = (sid: string) => {
|
||||||
|
const data = store.get(sid);
|
||||||
|
if (data) {
|
||||||
|
data.status = 'stopped';
|
||||||
|
store.set(sid, data);
|
||||||
|
syncToAirtable(sid, data);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// experiment-level operations
|
||||||
|
export const createExperiment = (sid: string, expId: string) => {
|
||||||
|
const exp: ExperimentData = {
|
||||||
|
id: expId,
|
||||||
|
status: 'active',
|
||||||
|
sessionIds: [sid],
|
||||||
|
createdAt: Date.now(),
|
||||||
|
};
|
||||||
|
experiments.set(expId, exp);
|
||||||
|
setExperiment(sid, expId); // link session to experiment
|
||||||
|
console.log(`experiment ${expId} started with session ${sid}`);
|
||||||
|
return exp;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stopExperimentById = (expId: string) => {
|
||||||
|
const exp = experiments.get(expId);
|
||||||
|
if (exp) {
|
||||||
|
exp.status = 'stopped';
|
||||||
|
experiments.set(expId, exp);
|
||||||
|
console.log(`experiment ${expId} stopped`);
|
||||||
|
}
|
||||||
|
return exp;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getExperiment = (expId: string) => experiments.get(expId);
|
||||||
|
|
||||||
|
export const getAllExperiments = () => Array.from(experiments.values());
|
||||||
Reference in New Issue
Block a user