feat: add rSocials module with campaign builder proxy
Registers rsocials module in rSpace unified system. Embeds the campaign
strategy builder from rsocials:3000 via iframe in the rSpace shell, with
API proxy for campaign CRUD. Accessible at /{space}/rsocials/campaign.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
09b5d1a3fa
commit
b218bf3a86
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* rSocials module — campaign strategy workflow builder.
|
||||
*
|
||||
* Proxies campaign API + embeds the Next.js campaign editor from rsocials:3000.
|
||||
* Page routes render an iframe inside the rSpace shell so the campaign builder
|
||||
* gets the full rSpace header/nav while the Next.js app runs independently.
|
||||
*/
|
||||
|
||||
import { Hono } from "hono";
|
||||
import { renderShell } from "../../server/shell";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
|
||||
const RSOCIALS_INTERNAL = process.env.RSOCIALS_URL || "http://rsocials:3000";
|
||||
const RSOCIALS_PUBLIC = "https://rsocials.online";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
// ── API proxy ────────────────────────────────────────────
|
||||
// Proxy all campaign API calls to the rsocials container
|
||||
|
||||
routes.get("/api/campaigns", async (c) => {
|
||||
const res = await fetch(`${RSOCIALS_INTERNAL}/api/campaigns`);
|
||||
return c.json(await res.json(), res.status as StatusCode);
|
||||
});
|
||||
|
||||
routes.get("/api/campaigns/:id", async (c) => {
|
||||
const res = await fetch(`${RSOCIALS_INTERNAL}/api/campaigns/${c.req.param("id")}`);
|
||||
return c.json(await res.json(), res.status as StatusCode);
|
||||
});
|
||||
|
||||
routes.post("/api/campaigns", async (c) => {
|
||||
const body = await c.req.text();
|
||||
const res = await fetch(`${RSOCIALS_INTERNAL}/api/campaigns`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body,
|
||||
});
|
||||
return c.json(await res.json(), res.status as StatusCode);
|
||||
});
|
||||
|
||||
routes.put("/api/campaigns/:id", async (c) => {
|
||||
const body = await c.req.text();
|
||||
const res = await fetch(`${RSOCIALS_INTERNAL}/api/campaigns/${c.req.param("id")}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body,
|
||||
});
|
||||
return c.json(await res.json(), res.status as StatusCode);
|
||||
});
|
||||
|
||||
routes.delete("/api/campaigns/:id", async (c) => {
|
||||
const res = await fetch(`${RSOCIALS_INTERNAL}/api/campaigns/${c.req.param("id")}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
return c.json(await res.json(), res.status as StatusCode);
|
||||
});
|
||||
|
||||
// ── Page routes ──────────────────────────────────────────
|
||||
|
||||
function campaignFrame(src: string, space: string, title: string) {
|
||||
return renderShell({
|
||||
title,
|
||||
moduleId: "rsocials",
|
||||
spaceSlug: space,
|
||||
modules: getModuleInfoList(),
|
||||
theme: "dark",
|
||||
styles: `<style>
|
||||
main.rsocials-frame { padding: 0 !important; margin: 0; }
|
||||
.rsocials-iframe {
|
||||
width: 100%; border: none;
|
||||
height: calc(100vh - 57px);
|
||||
display: block;
|
||||
}
|
||||
</style>`,
|
||||
body: `<main class="rsocials-frame">
|
||||
<iframe class="rsocials-iframe" src="${src}" allow="clipboard-write"></iframe>
|
||||
</main>`,
|
||||
});
|
||||
}
|
||||
|
||||
// /rsocials/ → campaign list
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
return c.html(
|
||||
campaignFrame(`${RSOCIALS_PUBLIC}/campaigns`, space, `Campaigns | rSocials`)
|
||||
);
|
||||
});
|
||||
|
||||
// /rsocials/campaign → campaign list (alias)
|
||||
routes.get("/campaign", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
return c.html(
|
||||
campaignFrame(`${RSOCIALS_PUBLIC}/campaigns`, space, `Campaigns | rSocials`)
|
||||
);
|
||||
});
|
||||
|
||||
// /rsocials/campaign/:id → specific campaign editor
|
||||
routes.get("/campaign/:id", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const id = c.req.param("id");
|
||||
return c.html(
|
||||
campaignFrame(
|
||||
`${RSOCIALS_PUBLIC}/campaigns/${id}`,
|
||||
space,
|
||||
`Campaign Editor | rSocials`
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
type StatusCode = 200 | 201 | 204 | 400 | 401 | 403 | 404 | 409 | 500 | 502;
|
||||
|
||||
export const rsocialsModule: RSpaceModule = {
|
||||
id: "rsocials",
|
||||
name: "rSocials",
|
||||
icon: "\uD83D\uDCE2",
|
||||
description: "Campaign strategy workflow builder",
|
||||
routes,
|
||||
standaloneDomain: "rsocials.online",
|
||||
};
|
||||
|
|
@ -63,6 +63,7 @@ import { inboxModule } from "../modules/inbox/mod";
|
|||
import { dataModule } from "../modules/data/mod";
|
||||
import { splatModule } from "../modules/splat/mod";
|
||||
import { photosModule } from "../modules/photos/mod";
|
||||
import { rsocialsModule } from "../modules/rsocials/mod";
|
||||
import { spaces } from "./spaces";
|
||||
import { renderShell, renderModuleLanding } from "./shell";
|
||||
import { syncServer } from "./sync-instance";
|
||||
|
|
@ -92,6 +93,7 @@ registerModule(inboxModule);
|
|||
registerModule(dataModule);
|
||||
registerModule(splatModule);
|
||||
registerModule(photosModule);
|
||||
registerModule(rsocialsModule);
|
||||
|
||||
// ── Config ──
|
||||
const PORT = Number(process.env.PORT) || 3000;
|
||||
|
|
|
|||
Loading…
Reference in New Issue