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
138716660b
commit
ba8a87727e
|
|
@ -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 { dataModule } from "../modules/data/mod";
|
||||||
import { splatModule } from "../modules/splat/mod";
|
import { splatModule } from "../modules/splat/mod";
|
||||||
import { photosModule } from "../modules/photos/mod";
|
import { photosModule } from "../modules/photos/mod";
|
||||||
|
import { rsocialsModule } from "../modules/rsocials/mod";
|
||||||
import { spaces } from "./spaces";
|
import { spaces } from "./spaces";
|
||||||
import { renderShell, renderModuleLanding } from "./shell";
|
import { renderShell, renderModuleLanding } from "./shell";
|
||||||
import { syncServer } from "./sync-instance";
|
import { syncServer } from "./sync-instance";
|
||||||
|
|
@ -92,6 +93,7 @@ registerModule(inboxModule);
|
||||||
registerModule(dataModule);
|
registerModule(dataModule);
|
||||||
registerModule(splatModule);
|
registerModule(splatModule);
|
||||||
registerModule(photosModule);
|
registerModule(photosModule);
|
||||||
|
registerModule(rsocialsModule);
|
||||||
|
|
||||||
// ── Config ──
|
// ── Config ──
|
||||||
const PORT = Number(process.env.PORT) || 3000;
|
const PORT = Number(process.env.PORT) || 3000;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue