Skip to content
2 changes: 1 addition & 1 deletion bridge/src/connectors/mariadb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import mysql, {
import { loadLocalMigrations, writeBaselineMigration } from "../utils/baselineMigration";
import crypto from "crypto";
import fs from "fs";
import { ensureDir, getMigrationsDir } from "../services/dbStore";
import { ensureDir, getMigrationsDir } from "../utils/config";
import {
CacheEntry,
CACHE_TTL,
Expand Down
2 changes: 1 addition & 1 deletion bridge/src/connectors/mysql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import mysql, {
import { loadLocalMigrations, writeBaselineMigration } from "../utils/baselineMigration";
import crypto from "crypto";
import fs from "fs";
import { ensureDir, getMigrationsDir } from "../services/dbStore";
import { ensureDir, getMigrationsDir } from "../utils/config";
import {
CacheEntry,
CACHE_TTL,
Expand Down
2 changes: 1 addition & 1 deletion bridge/src/connectors/postgres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Readable } from "stream";
import { loadLocalMigrations, writeBaselineMigration } from "../utils/baselineMigration";
import crypto from "crypto";
import fs from "fs";
import { ensureDir, getMigrationsDir } from "../services/dbStore";
import { ensureDir, getMigrationsDir } from "../utils/config";
import {
CacheEntry,
CACHE_TTL,
Expand Down
2 changes: 1 addition & 1 deletion bridge/src/handlers/migrationHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Rpc } from "../types";
import { DatabaseService } from "../services/databaseService";
import { QueryExecutor } from "../services/queryExecutor";
import { Logger } from "pino";
import { getMigrationsDir } from "../services/dbStore";
import { getMigrationsDir } from "../utils/config";
import path from "path";
import fs from "fs";

Expand Down
329 changes: 329 additions & 0 deletions bridge/src/handlers/projectHandlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
import { Rpc } from "../types";
import { Logger } from "pino";
import { projectStoreInstance } from "../services/projectStore";

/**
* RPC handlers for project CRUD and sub-resource operations.
* Mirrors the DatabaseHandlers pattern.
*/
export class ProjectHandlers {
constructor(
private rpc: Rpc,
private logger: Logger
) { }


async handleListProjects(_params: any, id: number | string) {
try {
const projects = await projectStoreInstance.listProjects();
this.rpc.sendResponse(id, { ok: true, data: projects });
} catch (e: any) {
this.logger?.error({ e }, "project.list failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleGetProject(params: any, id: number | string) {
try {
const { id: projectId } = params || {};
if (!projectId) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing id",
});
}

const project = await projectStoreInstance.getProject(projectId);
if (!project) {
return this.rpc.sendError(id, {
code: "NOT_FOUND",
message: "Project not found",
});
}

this.rpc.sendResponse(id, { ok: true, data: project });
} catch (e: any) {
this.logger?.error({ e }, "project.get failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleGetProjectByDatabaseId(params: any, id: number | string) {
try {
const { databaseId } = params || {};
if (!databaseId) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing databaseId",
});
}

const project = await projectStoreInstance.getProjectByDatabaseId(databaseId);
// Return null (not an error) when no project is linked
this.rpc.sendResponse(id, { ok: true, data: project });
} catch (e: any) {
this.logger?.error({ e }, "project.getByDatabaseId failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleCreateProject(params: any, id: number | string) {
try {
const { databaseId, name, description, defaultSchema } = params || {};
if (!databaseId || !name) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing databaseId or name",
});
}

const project = await projectStoreInstance.createProject({
databaseId,
name,
description,
defaultSchema,
});

this.rpc.sendResponse(id, { ok: true, data: project });
} catch (e: any) {
this.logger?.error({ e }, "project.create failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleUpdateProject(params: any, id: number | string) {
try {
const { id: projectId, ...updates } = params || {};
if (!projectId) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing id",
});
}

const project = await projectStoreInstance.updateProject(projectId, updates);
if (!project) {
return this.rpc.sendError(id, {
code: "NOT_FOUND",
message: "Project not found",
});
}

this.rpc.sendResponse(id, { ok: true, data: project });
} catch (e: any) {
this.logger?.error({ e }, "project.update failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleDeleteProject(params: any, id: number | string) {
try {
const { id: projectId } = params || {};
if (!projectId) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing id",
});
}

await projectStoreInstance.deleteProject(projectId);
this.rpc.sendResponse(id, { ok: true });
} catch (e: any) {
this.logger?.error({ e }, "project.delete failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleGetSchema(params: any, id: number | string) {
try {
const { projectId } = params || {};
if (!projectId) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing projectId",
});
}

const schema = await projectStoreInstance.getSchema(projectId);
this.rpc.sendResponse(id, { ok: true, data: schema });
} catch (e: any) {
this.logger?.error({ e }, "project.getSchema failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleSaveSchema(params: any, id: number | string) {
try {
const { projectId, schemas } = params || {};
if (!projectId || !schemas) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing projectId or schemas",
});
}

const result = await projectStoreInstance.saveSchema(projectId, schemas);
this.rpc.sendResponse(id, { ok: true, data: result });
} catch (e: any) {
this.logger?.error({ e }, "project.saveSchema failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleGetERDiagram(params: any, id: number | string) {
try {
const { projectId } = params || {};
if (!projectId) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing projectId",
});
}

const diagram = await projectStoreInstance.getERDiagram(projectId);
this.rpc.sendResponse(id, { ok: true, data: diagram });
} catch (e: any) {
this.logger?.error({ e }, "project.getERDiagram failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleSaveERDiagram(params: any, id: number | string) {
try {
const { projectId, nodes, zoom, panX, panY } = params || {};
if (!projectId || !nodes) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing projectId or nodes",
});
}

const result = await projectStoreInstance.saveERDiagram(projectId, {
nodes,
zoom,
panX,
panY,
});
this.rpc.sendResponse(id, { ok: true, data: result });
} catch (e: any) {
this.logger?.error({ e }, "project.saveERDiagram failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleGetQueries(params: any, id: number | string) {
try {
const { projectId } = params || {};
if (!projectId) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing projectId",
});
}

const queries = await projectStoreInstance.getQueries(projectId);
this.rpc.sendResponse(id, { ok: true, data: queries });
} catch (e: any) {
this.logger?.error({ e }, "project.getQueries failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleAddQuery(params: any, id: number | string) {
try {
const { projectId, name, sql, description } = params || {};
if (!projectId || !name || !sql) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing projectId, name, or sql",
});
}

const query = await projectStoreInstance.addQuery(projectId, {
name,
sql,
description,
});
this.rpc.sendResponse(id, { ok: true, data: query });
} catch (e: any) {
this.logger?.error({ e }, "project.addQuery failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleUpdateQuery(params: any, id: number | string) {
try {
const { projectId, queryId, ...updates } = params || {};
if (!projectId || !queryId) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing projectId or queryId",
});
}

const query = await projectStoreInstance.updateQuery(
projectId,
queryId,
updates
);
if (!query) {
return this.rpc.sendError(id, {
code: "NOT_FOUND",
message: "Query not found",
});
}

this.rpc.sendResponse(id, { ok: true, data: query });
} catch (e: any) {
this.logger?.error({ e }, "project.updateQuery failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

async handleDeleteQuery(params: any, id: number | string) {
try {
const { projectId, queryId } = params || {};
if (!projectId || !queryId) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing projectId or queryId",
});
}

await projectStoreInstance.deleteQuery(projectId, queryId);
this.rpc.sendResponse(id, { ok: true });
} catch (e: any) {
this.logger?.error({ e }, "project.deleteQuery failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}

// ==========================================
// Export (for future git-native support)
// ==========================================

async handleExportProject(params: any, id: number | string) {
try {
const { projectId } = params || {};
if (!projectId) {
return this.rpc.sendError(id, {
code: "BAD_REQUEST",
message: "Missing projectId",
});
}

const bundle = await projectStoreInstance.exportProject(projectId);
if (!bundle) {
return this.rpc.sendError(id, {
code: "NOT_FOUND",
message: "Project not found",
});
}

this.rpc.sendResponse(id, { ok: true, data: bundle });
} catch (e: any) {
this.logger?.error({ e }, "project.export failed");
this.rpc.sendError(id, { code: "IO_ERROR", message: String(e) });
}
}
}
Loading