God Mode - Complete Parasite Application

This commit is contained in:
cawcenter
2025-12-15 18:22:03 -05:00
parent f658f76941
commit 6e826de942
38 changed files with 2153 additions and 201 deletions

View File

@@ -1,37 +1,15 @@
import { query } from '../db';
/**
* Directus Shim for Valhalla
* Translates Directus SDK calls to Raw SQL (Server) or Proxy API (Client).
*/
const isServer = typeof window === 'undefined';
import type { Query } from './types';
const PROXY_ENDPOINT = '/api/god/proxy';
// --- Types ---
interface QueryCmp {
_eq?: any;
_neq?: any;
_gt?: any;
_lt?: any;
_contains?: any;
_in?: any[];
}
interface QueryFilter {
[field: string]: QueryCmp | QueryFilter | any;
_or?: QueryFilter[];
_and?: QueryFilter[];
}
interface Query {
filter?: QueryFilter;
fields?: string[];
limit?: number;
offset?: number;
sort?: string[];
aggregate?: any;
}
// Re-export types for consumers
export * from './types';
// --- SDK Mocks ---
@@ -68,8 +46,10 @@ export function aggregate(collection: string, q?: Query) {
export function getDirectusClient() {
return {
request: async (command: any) => {
if (isServer) {
// SERVER-SIDE: Direct DB Access
// Check if running on server via import.meta.env provided by Vite/Astro
if (import.meta.env.SSR) {
// SERVER-SIDE: Dynamic import to avoid bundling 'pg' in client
const { executeCommand } = await import('./server');
return await executeCommand(command);
} else {
// CLIENT-SIDE: Proxy via HTTP
@@ -82,7 +62,7 @@ export function getDirectusClient() {
// --- Proxy Execution (Client) ---
async function executeProxy(command: any) {
const token = localStorage.getItem('godToken') || ''; // Assuming auth token storage
const token = typeof localStorage !== 'undefined' ? localStorage.getItem('godToken') : '';
const res = await fetch(PROXY_ENDPOINT, {
method: 'POST',
headers: {
@@ -100,174 +80,3 @@ async function executeProxy(command: any) {
return await res.json();
}
// --- Server Execution (Server) ---
// This is exported so the Proxy Endpoint can use it too!
export async function executeCommand(command: any) {
try {
switch (command.type) {
case 'readItems':
return await executeReadItems(command.collection, command.query);
case 'readItem':
return await executeReadItem(command.collection, command.id, command.query);
case 'createItem':
return await executeCreateItem(command.collection, command.data);
case 'updateItem':
return await executeUpdateItem(command.collection, command.id, command.data);
case 'deleteItem':
return await executeDeleteItem(command.collection, command.id);
case 'aggregate':
return await executeAggregate(command.collection, command.query);
default:
throw new Error(`Unknown command type: ${command.type}`);
}
} catch (err: any) {
console.error(`Shim Error (${command.type} on ${command.collection}):`, err);
throw err;
}
}
// --- SQL Builders ---
async function executeReadItems(collection: string, q: Query = {}) {
// SECURITY: Validate collection name to prevent SQL injection via simple table name abuse
if (!/^[a-zA-Z0-9_]+$/.test(collection)) throw new Error("Invalid collection name");
let sql = `SELECT ${buildSelectFields(q.fields)} FROM "${collection}"`;
const params: any[] = [];
if (q.filter) {
const { where, vals } = buildWhere(q.filter, params);
if (where) sql += ` WHERE ${where}`;
}
// Sort
if (q.sort) {
const orderBy = q.sort.map(s => {
const desc = s.startsWith('-');
const field = desc ? s.substring(1) : s;
if (!/^[a-zA-Z0-9_]+$/.test(field)) return 'id'; // sanitize
return `"${field}" ${desc ? 'DESC' : 'ASC'}`;
}).join(', ');
if (orderBy) sql += ` ORDER BY ${orderBy}`;
}
// Limit/Offset
if (q.limit !== undefined && q.limit !== -1) sql += ` LIMIT ${q.limit}`;
if (q.offset) sql += ` OFFSET ${q.offset}`;
const res = await query(sql, params);
return res.rows;
}
async function executeReadItem(collection: string, id: string | number, q: Query = {}) {
if (!/^[a-zA-Z0-9_]+$/.test(collection)) throw new Error("Invalid collection name");
const res = await query(`SELECT * FROM "${collection}" WHERE id = $1`, [id]);
return res.rows[0];
}
async function executeCreateItem(collection: string, data: any) {
if (!/^[a-zA-Z0-9_]+$/.test(collection)) throw new Error("Invalid collection name");
const keys = Object.keys(data);
const vals = Object.values(data);
const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ');
const cols = keys.map(k => `"${k}"`).join(', ');
const sql = `INSERT INTO "${collection}" (${cols}) VALUES (${placeholders}) RETURNING *`;
const res = await query(sql, vals);
return res.rows[0];
}
async function executeUpdateItem(collection: string, id: string | number, data: any) {
if (!/^[a-zA-Z0-9_]+$/.test(collection)) throw new Error("Invalid collection name");
const keys = Object.keys(data);
const vals = Object.values(data);
const setClause = keys.map((k, i) => `"${k}" = $${i + 2}`).join(', ');
const sql = `UPDATE "${collection}" SET ${setClause} WHERE id = $1 RETURNING *`;
const res = await query(sql, [id, ...vals]);
return res.rows[0];
}
async function executeDeleteItem(collection: string, id: string | number) {
if (!/^[a-zA-Z0-9_]+$/.test(collection)) throw new Error("Invalid collection name");
await query(`DELETE FROM "${collection}" WHERE id = $1`, [id]);
return true;
}
async function executeAggregate(collection: string, q: Query = {}) {
if (!/^[a-zA-Z0-9_]+$/.test(collection)) throw new Error("Invalid collection name");
if (q.aggregate?.count) {
let sql = `SELECT COUNT(*) as count FROM "${collection}"`;
const params: any[] = [];
if (q.filter) {
const { where, vals } = buildWhere(q.filter, params);
if (where) sql += ` WHERE ${where}`;
}
const res = await query(sql, params);
return [{ count: res.rows[0].count }];
}
return [];
}
// --- Query Helpers ---
function buildSelectFields(fields?: string[]) {
if (!fields || fields.includes('*') || fields.length === 0) return '*';
const cleanFields = fields.filter(f => typeof f === 'string');
if (cleanFields.length === 0) return '*';
return cleanFields.map(f => `"${f.replace(/[^a-zA-Z0-9_]/g, '')}"`).join(', ');
}
function buildWhere(filter: QueryFilter, params: any[]): { where: string, vals: any[] } {
const conditions: string[] = [];
if (filter._or) {
const orConds = filter._or.map(f => {
const res = buildWhere(f, params);
return `(${res.where})`;
});
conditions.push(`(${orConds.join(' OR ')})`);
return { where: conditions.join(' AND '), vals: params };
}
if (filter._and) {
const andConds = filter._and.map(f => {
const res = buildWhere(f, params);
return `(${res.where})`;
});
conditions.push(`(${andConds.join(' AND ')})`);
return { where: conditions.join(' AND '), vals: params };
}
for (const [key, val] of Object.entries(filter)) {
if (key.startsWith('_')) continue;
if (!/^[a-zA-Z0-9_]+$/.test(key)) continue; // skip invalid keys
if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
for (const [op, opVal] of Object.entries(val)) {
if (op === '_eq') {
params.push(opVal);
conditions.push(`"${key}" = $${params.length}`);
} else if (op === '_neq') {
params.push(opVal);
conditions.push(`"${key}" != $${params.length}`);
} else if (op === '_contains') {
params.push(`%${opVal}%`);
conditions.push(`"${key}" LIKE $${params.length}`);
} else if (op === '_gt') {
params.push(opVal);
conditions.push(`"${key}" > $${params.length}`);
} else if (op === '_lt') {
params.push(opVal);
conditions.push(`"${key}" < $${params.length}`);
}
}
} else {
params.push(val);
conditions.push(`"${key}" = $${params.length}`);
}
}
return { where: conditions.join(' AND '), vals: params };
}

172
src/lib/directus/server.ts Normal file
View File

@@ -0,0 +1,172 @@
import { query } from '../db';
import type { Query, QueryFilter } from './types';
// --- Server Execution (Server) ---
export async function executeCommand(command: any) {
try {
switch (command.type) {
case 'readItems':
return await executeReadItems(command.collection, command.query);
case 'readItem':
return await executeReadItem(command.collection, command.id, command.query);
case 'createItem':
return await executeCreateItem(command.collection, command.data);
case 'updateItem':
return await executeUpdateItem(command.collection, command.id, command.data);
case 'deleteItem':
return await executeDeleteItem(command.collection, command.id);
case 'aggregate':
return await executeAggregate(command.collection, command.query);
default:
throw new Error(`Unknown command type: ${command.type}`);
}
} catch (err: any) {
console.error(`Shim Error (${command.type} on ${command.collection}):`, err);
throw err;
}
}
// --- SQL Builders ---
async function executeReadItems(collection: string, q: Query = {}) {
// SECURITY: Validate collection name to prevent SQL injection via simple table name abuse
if (!/^[a-zA-Z0-9_]+$/.test(collection)) throw new Error("Invalid collection name");
let sql = `SELECT ${buildSelectFields(q.fields)} FROM "${collection}"`;
const params: any[] = [];
if (q.filter) {
const { where, vals } = buildWhere(q.filter, params);
if (where) sql += ` WHERE ${where}`;
}
// Sort
if (q.sort) {
const orderBy = q.sort.map(s => {
const desc = s.startsWith('-');
const field = desc ? s.substring(1) : s;
if (!/^[a-zA-Z0-9_]+$/.test(field)) return 'id'; // sanitize
return `"${field}" ${desc ? 'DESC' : 'ASC'}`;
}).join(', ');
if (orderBy) sql += ` ORDER BY ${orderBy}`;
}
// Limit/Offset
if (q.limit !== undefined && q.limit !== -1) sql += ` LIMIT ${q.limit}`;
if (q.offset) sql += ` OFFSET ${q.offset}`;
const res = await query(sql, params);
return res.rows;
}
async function executeReadItem(collection: string, id: string | number, q: Query = {}) {
if (!/^[a-zA-Z0-9_]+$/.test(collection)) throw new Error("Invalid collection name");
const res = await query(`SELECT * FROM "${collection}" WHERE id = $1`, [id]);
return res.rows[0];
}
async function executeCreateItem(collection: string, data: any) {
if (!/^[a-zA-Z0-9_]+$/.test(collection)) throw new Error("Invalid collection name");
const keys = Object.keys(data);
const vals = Object.values(data);
const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ');
const cols = keys.map(k => `"${k}"`).join(', ');
const sql = `INSERT INTO "${collection}" (${cols}) VALUES (${placeholders}) RETURNING *`;
const res = await query(sql, vals);
return res.rows[0];
}
async function executeUpdateItem(collection: string, id: string | number, data: any) {
if (!/^[a-zA-Z0-9_]+$/.test(collection)) throw new Error("Invalid collection name");
const keys = Object.keys(data);
const vals = Object.values(data);
const setClause = keys.map((k, i) => `"${k}" = $${i + 2}`).join(', ');
const sql = `UPDATE "${collection}" SET ${setClause} WHERE id = $1 RETURNING *`;
const res = await query(sql, [id, ...vals]);
return res.rows[0];
}
async function executeDeleteItem(collection: string, id: string | number) {
if (!/^[a-zA-Z0-9_]+$/.test(collection)) throw new Error("Invalid collection name");
await query(`DELETE FROM "${collection}" WHERE id = $1`, [id]);
return true;
}
async function executeAggregate(collection: string, q: Query = {}) {
if (!/^[a-zA-Z0-9_]+$/.test(collection)) throw new Error("Invalid collection name");
if (q.aggregate?.count) {
let sql = `SELECT COUNT(*) as count FROM "${collection}"`;
const params: any[] = [];
if (q.filter) {
const { where, vals } = buildWhere(q.filter, params);
if (where) sql += ` WHERE ${where}`;
}
const res = await query(sql, params);
return [{ count: res.rows[0].count }];
}
return [];
}
// --- Query Helpers ---
function buildSelectFields(fields?: string[]) {
if (!fields || fields.includes('*') || fields.length === 0) return '*';
const cleanFields = fields.filter(f => typeof f === 'string');
if (cleanFields.length === 0) return '*';
return cleanFields.map(f => `"${f.replace(/[^a-zA-Z0-9_]/g, '')}"`).join(', ');
}
function buildWhere(filter: QueryFilter, params: any[]): { where: string, vals: any[] } {
const conditions: string[] = [];
if (filter._or) {
const orConds = filter._or.map(f => {
const res = buildWhere(f, params);
return `(${res.where})`;
});
conditions.push(`(${orConds.join(' OR ')})`);
return { where: conditions.join(' AND '), vals: params };
}
if (filter._and) {
const andConds = filter._and.map(f => {
const res = buildWhere(f, params);
return `(${res.where})`;
});
conditions.push(`(${andConds.join(' AND ')})`);
return { where: conditions.join(' AND '), vals: params };
}
for (const [key, val] of Object.entries(filter)) {
if (key.startsWith('_')) continue;
if (!/^[a-zA-Z0-9_]+$/.test(key)) continue; // skip invalid keys
if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
for (const [op, opVal] of Object.entries(val)) {
if (op === '_eq') {
params.push(opVal);
conditions.push(`"${key}" = $${params.length}`);
} else if (op === '_neq') {
params.push(opVal);
conditions.push(`"${key}" != $${params.length}`);
} else if (op === '_contains') {
params.push(`%${opVal}%`);
conditions.push(`"${key}" LIKE $${params.length}`);
} else if (op === '_gt') {
params.push(opVal);
conditions.push(`"${key}" > $${params.length}`);
} else if (op === '_lt') {
params.push(opVal);
conditions.push(`"${key}" < $${params.length}`);
}
}
} else {
params.push(val);
conditions.push(`"${key}" = $${params.length}`);
}
}
return { where: conditions.join(' AND '), vals: params };
}

23
src/lib/directus/types.ts Normal file
View File

@@ -0,0 +1,23 @@
export interface QueryCmp {
_eq?: any;
_neq?: any;
_gt?: any;
_lt?: any;
_contains?: any;
_in?: any[];
}
export interface QueryFilter {
[field: string]: QueryCmp | QueryFilter | any;
_or?: QueryFilter[];
_and?: QueryFilter[];
}
export interface Query {
filter?: QueryFilter;
fields?: string[];
limit?: number;
offset?: number;
sort?: string[];
aggregate?: any;
}