diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index c6fb1f5..6cc74c5 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -4,6 +4,7 @@ import { createServer } from "http"; import path from "path"; import { fileURLToPath } from "url"; import { initWebSocket, getClientCount } from "./services/ws.service.js"; +import workspaceRouter from "./routes/workspace.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const app = express(); @@ -22,6 +23,8 @@ app.get("/api/health", (_req, res) => { }); }); +app.use("/api/workspace", workspaceRouter); + const frontendDist = path.resolve(__dirname, "../../frontend/dist"); app.use(express.static(frontendDist)); app.get("*path", (_req, res) => { diff --git a/packages/backend/src/routes/workspace.ts b/packages/backend/src/routes/workspace.ts new file mode 100644 index 0000000..feafb0e --- /dev/null +++ b/packages/backend/src/routes/workspace.ts @@ -0,0 +1,29 @@ +import { Router } from "express"; +import { + ensureWorkspaceStructure, + readConfig, + writeConfig, + getWorkspacePath, +} from "../services/workspace.service.js"; + +const router = Router(); + +router.get("/config", async (_req, res) => { + const config = await readConfig(); + const sanitized = { ...config, githubPat: config.githubPat ? "***" : undefined }; + res.json(sanitized); +}); + +router.put("/config", async (req, res) => { + const current = await readConfig(); + const updated = { ...current, ...req.body }; + await writeConfig(updated); + res.json({ ok: true }); +}); + +router.post("/init", async (_req, res) => { + await ensureWorkspaceStructure(); + res.json({ ok: true, path: getWorkspacePath() }); +}); + +export default router; diff --git a/packages/backend/src/services/workspace.service.ts b/packages/backend/src/services/workspace.service.ts new file mode 100644 index 0000000..1e327a0 --- /dev/null +++ b/packages/backend/src/services/workspace.service.ts @@ -0,0 +1,67 @@ +import fs from "fs/promises"; +import path from "path"; + +const WORKSPACE_PATH = process.env.WORKSPACE_PATH || "/workspace"; + +interface ForgeConfig { + githubPat?: string; + workspacePath: string; + nwnHomePath: string; + setupComplete: boolean; + editorState?: { + openTabs: string[]; + activeTab?: string; + cursorPositions: Record; + }; +} + +const REQUIRED_DIRS = [ + "repos", + "server/modules", + "server/hak", + "server/tlk", + "server/servervault", + "server/database", + "server/development", + "server/override", + "server/portraits", + "logs", + "config", +]; + +export async function ensureWorkspaceStructure(): Promise { + for (const dir of REQUIRED_DIRS) { + await fs.mkdir(path.join(WORKSPACE_PATH, dir), { recursive: true }); + } +} + +export async function readConfig(): Promise { + const configPath = path.join(WORKSPACE_PATH, "config", "forge.json"); + try { + const raw = await fs.readFile(configPath, "utf-8"); + return JSON.parse(raw); + } catch { + return { + workspacePath: WORKSPACE_PATH, + nwnHomePath: process.env.NWN_HOME_PATH || "", + setupComplete: false, + }; + } +} + +export async function writeConfig(config: ForgeConfig): Promise { + const configPath = path.join(WORKSPACE_PATH, "config", "forge.json"); + await fs.writeFile(configPath, JSON.stringify(config, null, 2)); +} + +export function getWorkspacePath(): string { + return WORKSPACE_PATH; +} + +export function getServerPath(...segments: string[]): string { + return path.join(WORKSPACE_PATH, "server", ...segments); +} + +export function getRepoPath(repo: string, ...segments: string[]): string { + return path.join(WORKSPACE_PATH, "repos", repo, ...segments); +}