import { withIronSessionApiRoute, withIronSessionSsr } from "iron-session/next";
import {
GetServerSidePropsContext,
GetServerSidePropsResult,
NextApiHandler,
} from "next";
import { IronSession, IronSessionOptions } from "iron-session";
import {
JsonTree, ZipConfig
} from "@react-awesome-query-builder/core";
import { nanoid } from "nanoid";
import { IncomingMessage } from "http";
import { Redis } from "@upstash/redis";
import jsonfile from "jsonfile";
import { existsSync } from "node:fs";
// API to manage session data + wrappers to enable session for routes and SSR
export type SessionData = {
jsonTree?: JsonTree;
zipConfig?: ZipConfig;
};
export type Session = IronSession & {
id: string;
};
// Wrappers
export const sessionOptions: IronSessionOptions = {
cookieName: "raqb_sandbox",
password: "complex_password_at_least_32_characters_long",
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
cookieOptions: {
secure: process.env.NODE_ENV === "production",
},
};
export function withSessionRoute(handler: NextApiHandler) {
return withIronSessionApiRoute(handler, sessionOptions);
}
export function withSessionSsr<
P extends { [key: string]: unknown } = { [key: string]: unknown },
>(
handler: (
context: GetServerSidePropsContext,
) => GetServerSidePropsResult
| Promise>,
) {
return withIronSessionSsr(handler, sessionOptions);
}
declare module "next" {
interface NextApiRequest {
session: Session;
}
}
// Manage session data storage - Redis or local tmp file
const redis = process.env.UPSTASH_REDIS_REST_URL ? new Redis({
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN,
}) : null;
export const saveSessionData = async (req: IncomingMessage, newData: SessionData) => {
const session = (req.session as Session);
if (!session.id) {
session.id = nanoid();
await session.save();
}
const sid = session.id;
if (redis) {
let data = await getSessionData(req);
if (!data) {
data = {};
await redis.json.set(`sessions.${sid}`, "$", data);
}
if (newData.jsonTree) {
await redis.json.set(`sessions.${sid}`, "$.jsonTree", newData.jsonTree as any as Record);
}
if (newData.zipConfig) {
await redis.json.set(`sessions.${sid}`, "$.zipConfig", newData.zipConfig);
}
} else {
const data = {
...(await getSessionData(req)),
...newData,
};
jsonfile.writeFileSync(`/tmp/sessions_${sid}`, data);
}
};
export const getSessionData = async (req: IncomingMessage): Promise => {
const sid = (req.session as Session).id;
if (redis) {
return await redis.json.get(`sessions.${sid}`) as SessionData;
} else {
return existsSync(`/tmp/sessions_${sid}`) ? jsonfile.readFileSync(`/tmp/sessions_${sid}`) as SessionData : {};
}
};