diff --git a/docker-compose.yml b/docker-compose.yml
index 0819a1f..070aacd 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -136,6 +136,10 @@ services:
- "traefik.http.routers.rspace-rswag.entrypoints=web"
- "traefik.http.routers.rspace-rswag.priority=120"
- "traefik.http.routers.rspace-rswag.service=rspace-online"
+ - "traefik.http.routers.rspace-rsocials.rule=Host(`rsocials.online`)"
+ - "traefik.http.routers.rspace-rsocials.entrypoints=web"
+ - "traefik.http.routers.rspace-rsocials.priority=120"
+ - "traefik.http.routers.rspace-rsocials.service=rspace-online"
# Service configuration
- "traefik.http.services.rspace-online.loadbalancer.server.port=3000"
- "traefik.docker.network=traefik-public"
diff --git a/modules/rsocials/mod.ts b/modules/rsocials/mod.ts
index f5bb6bd..54383f9 100644
--- a/modules/rsocials/mod.ts
+++ b/modules/rsocials/mod.ts
@@ -1,120 +1,199 @@
/**
- * rSocials module — campaign strategy workflow builder.
+ * Socials module — federated social feed aggregator.
*
- * 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.
+ * Aggregates and displays social media activity across community members.
+ * Supports ActivityPub, RSS, and manual link sharing.
*/
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";
+import type { RSpaceModule } from "../../shared/module";
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);
+// ── API: Health ──
+routes.get("/api/health", (c) => {
+ return c.json({ ok: true, module: "rsocials" });
});
-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,
+// ── API: Info ──
+routes.get("/api/info", (c) => {
+ return c.json({
+ module: "rsocials",
+ description: "Federated social feed aggregator for communities",
+ features: [
+ "ActivityPub integration",
+ "RSS feed aggregation",
+ "Link sharing",
+ "Community timeline",
+ ],
});
- 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,
+// ── API: Feed — community social timeline ──
+routes.get("/api/feed", (c) => {
+ // Demo feed items
+ return c.json({
+ items: [
+ {
+ id: "demo-1",
+ type: "post",
+ author: "Alice",
+ content: "Just published our community governance proposal!",
+ source: "fediverse",
+ timestamp: new Date(Date.now() - 3600_000).toISOString(),
+ likes: 12,
+ replies: 3,
+ },
+ {
+ id: "demo-2",
+ type: "link",
+ author: "Bob",
+ content: "Great article on local-first collaboration",
+ url: "https://example.com/local-first",
+ source: "shared",
+ timestamp: new Date(Date.now() - 7200_000).toISOString(),
+ likes: 8,
+ replies: 1,
+ },
+ {
+ id: "demo-3",
+ type: "post",
+ author: "Carol",
+ content: "Welcome new members! Check out rSpace's tools in the app switcher above.",
+ source: "local",
+ timestamp: new Date(Date.now() - 14400_000).toISOString(),
+ likes: 24,
+ replies: 7,
+ },
+ ],
+ demo: true,
});
- 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: ``,
- body: `