Merge branch 'dev'
This commit is contained in:
commit
6e5ddaface
|
|
@ -104,7 +104,7 @@ const routes = new Hono();
|
|||
// ── API: List books ──
|
||||
routes.get("/api/books", async (c) => {
|
||||
const space = c.req.param("space") || "global";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const search = c.req.query("search")?.toLowerCase();
|
||||
const tag = c.req.query("tag");
|
||||
const limit = Math.min(parseInt(c.req.query("limit") || "50"), 100);
|
||||
|
|
@ -157,7 +157,7 @@ routes.get("/api/books", async (c) => {
|
|||
// ── API: Upload book ──
|
||||
routes.post("/api/books", async (c) => {
|
||||
const space = c.req.param("space") || "global";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
|
|
@ -244,7 +244,7 @@ routes.post("/api/books", async (c) => {
|
|||
// ── API: Get book details ──
|
||||
routes.get("/api/books/:id", async (c) => {
|
||||
const space = c.req.param("space") || "global";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
|
@ -269,7 +269,7 @@ routes.get("/api/books/:id", async (c) => {
|
|||
// ── API: Serve PDF ──
|
||||
routes.get("/api/books/:id/pdf", async (c) => {
|
||||
const space = c.req.param("space") || "global";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
|
@ -307,7 +307,7 @@ routes.get("/api/books/:id/pdf", async (c) => {
|
|||
// ── Page: Library ──
|
||||
routes.get("/", (c) => {
|
||||
const spaceSlug = c.req.param("space") || "personal";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
return c.html(renderShell({
|
||||
title: `${spaceSlug} — Library | rSpace`,
|
||||
moduleId: "rbooks",
|
||||
|
|
@ -323,7 +323,7 @@ routes.get("/", (c) => {
|
|||
// ── Page: Book reader ──
|
||||
routes.get("/read/:id", async (c) => {
|
||||
const spaceSlug = c.req.param("space") || "personal";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ function seedDemoIfEmpty(space: string) {
|
|||
// GET /api/events — query events with filters
|
||||
routes.get("/api/events", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const { start, end, source, search, rTool, rEntityId, upcoming } = c.req.query();
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
|
@ -313,7 +313,7 @@ routes.post("/api/events", async (c) => {
|
|||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const body = await c.req.json();
|
||||
const { title, description, start_time, end_time, all_day, timezone, source_id, location_id, location_name,
|
||||
is_virtual, virtual_url, virtual_platform, r_tool_source, r_tool_entity_id,
|
||||
|
|
@ -398,7 +398,7 @@ routes.post("/api/events", async (c) => {
|
|||
// GET /api/events/scheduled — query only scheduled knowledge items
|
||||
routes.get("/api/events/scheduled", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const { date, upcoming, pending_only } = c.req.query();
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
|
@ -434,7 +434,7 @@ routes.get("/api/events/scheduled", async (c) => {
|
|||
// GET /api/events/:id
|
||||
routes.get("/api/events/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
||||
|
|
@ -451,7 +451,7 @@ routes.patch("/api/events/:id", async (c) => {
|
|||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
|
||||
|
|
@ -504,7 +504,7 @@ routes.patch("/api/events/:id", async (c) => {
|
|||
// DELETE /api/events/:id
|
||||
routes.delete("/api/events/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const docId = calendarDocId(dataSpace);
|
||||
|
|
@ -521,7 +521,7 @@ routes.delete("/api/events/:id", async (c) => {
|
|||
|
||||
routes.get("/api/sources", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const { is_active, is_visible, source_type } = c.req.query();
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
||||
|
|
@ -550,7 +550,7 @@ routes.post("/api/sources", async (c) => {
|
|||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const body = await c.req.json();
|
||||
const docId = calendarDocId(dataSpace);
|
||||
ensureDoc(dataSpace);
|
||||
|
|
@ -611,7 +611,7 @@ function deriveLocations(doc: CalendarDoc): DerivedLocation[] {
|
|||
|
||||
routes.get("/api/locations", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const { granularity, parent, search, root } = c.req.query();
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
||||
|
|
@ -638,7 +638,7 @@ routes.get("/api/locations", async (c) => {
|
|||
|
||||
routes.get("/api/locations/tree", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
||||
// Flat list with depth=0 since hierarchical parent_id data is not stored in Automerge
|
||||
|
|
@ -690,7 +690,7 @@ routes.get("/api/lunar", async (c) => {
|
|||
|
||||
routes.get("/api/stats", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
||||
const events = Object.values(doc.events).length;
|
||||
|
|
@ -704,7 +704,7 @@ routes.get("/api/stats", async (c) => {
|
|||
|
||||
routes.get("/api/context/:tool", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const tool = c.req.param("tool");
|
||||
const entityId = c.req.query("entityId");
|
||||
if (!entityId) return c.json({ error: "entityId required" }, 400);
|
||||
|
|
@ -721,7 +721,7 @@ routes.get("/api/context/:tool", async (c) => {
|
|||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Calendar | rSpace`,
|
||||
moduleId: "rcal",
|
||||
|
|
|
|||
|
|
@ -500,6 +500,10 @@ class FolkPaymentPage extends HTMLElement {
|
|||
};
|
||||
const explorer = explorerBase[p.chainId] || '';
|
||||
|
||||
const cartLink = p.linkedCartId
|
||||
? `<div style="margin-top:1.25rem"><a href="/${this.space}/rcart/carts" class="btn btn-primary" style="display:inline-block; text-decoration:none; text-align:center;">Return to Cart</a></div>`
|
||||
: '';
|
||||
|
||||
return `
|
||||
<div class="confirmation">
|
||||
<div class="confirm-icon">✓</div>
|
||||
|
|
@ -509,6 +513,7 @@ class FolkPaymentPage extends HTMLElement {
|
|||
${p.txHash ? `<div>Transaction: <a href="${explorer}${p.txHash}" target="_blank" rel="noopener">${p.txHash.slice(0, 10)}...${p.txHash.slice(-8)}</a></div>` : ''}
|
||||
${p.paid_at ? `<div>Paid: ${new Date(p.paid_at).toLocaleString()}</div>` : ''}
|
||||
</div>
|
||||
${cartLink}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -919,7 +919,7 @@ routes.get("/api/shopping-carts", async (c) => {
|
|||
// POST /api/shopping-carts — Create cart
|
||||
routes.post("/api/shopping-carts", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const { name, description = "", targetAmount = 0, currency = "USD" } = await c.req.json();
|
||||
const { name, description = "", targetAmount = 0, currency = "USD", recipientAddress = null } = await c.req.json();
|
||||
if (!name) return c.json({ error: "Required: name" }, 400);
|
||||
|
||||
const cartId = crypto.randomUUID();
|
||||
|
|
@ -934,6 +934,7 @@ routes.post("/api/shopping-carts", async (c) => {
|
|||
d.cart.name = name;
|
||||
d.cart.description = description;
|
||||
d.cart.status = 'OPEN';
|
||||
d.cart.recipientAddress = recipientAddress || null;
|
||||
d.cart.targetAmount = targetAmount;
|
||||
d.cart.fundedAmount = 0;
|
||||
d.cart.currency = currency;
|
||||
|
|
@ -969,6 +970,7 @@ routes.get("/api/shopping-carts/:cartId", async (c) => {
|
|||
name: doc.cart.name,
|
||||
description: doc.cart.description,
|
||||
status: doc.cart.status,
|
||||
recipientAddress: doc.cart.recipientAddress || null,
|
||||
targetAmount: doc.cart.targetAmount,
|
||||
fundedAmount: doc.cart.fundedAmount,
|
||||
currency: doc.cart.currency,
|
||||
|
|
@ -1191,6 +1193,58 @@ routes.post("/api/shopping-carts/:cartId/contribute", async (c) => {
|
|||
return c.json({ id: contribId, amount, fundedAmount: doc.cart.fundedAmount + amount }, 201);
|
||||
});
|
||||
|
||||
// POST /api/shopping-carts/:cartId/contribute-pay — Create payment request for cart contribution
|
||||
routes.post("/api/shopping-carts/:cartId/contribute-pay", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const cartId = c.req.param("cartId");
|
||||
const docId = shoppingCartDocId(space, cartId);
|
||||
const doc = _syncServer!.getDoc<ShoppingCartDoc>(docId);
|
||||
if (!doc) return c.json({ error: "Cart not found" }, 404);
|
||||
|
||||
if (!doc.cart.recipientAddress) {
|
||||
return c.json({ error: "Cart has no recipient wallet address" }, 400);
|
||||
}
|
||||
|
||||
const { amount, username = "Anonymous", chainId = 84532, token: payToken = "USDC" } = await c.req.json();
|
||||
if (typeof amount !== 'number' || amount <= 0) return c.json({ error: "amount must be > 0" }, 400);
|
||||
|
||||
const paymentId = crypto.randomUUID();
|
||||
const now = Date.now();
|
||||
const payDocId = paymentRequestDocId(space, paymentId);
|
||||
|
||||
const payDoc = Automerge.change(Automerge.init<PaymentRequestDoc>(), 'create cart contribution payment', (d) => {
|
||||
const init = paymentRequestSchema.init();
|
||||
Object.assign(d, init);
|
||||
d.meta.spaceSlug = space;
|
||||
d.payment.id = paymentId;
|
||||
d.payment.description = `Contribution to "${doc.cart.name}"`;
|
||||
d.payment.amount = String(amount);
|
||||
d.payment.amountEditable = false;
|
||||
d.payment.token = payToken;
|
||||
d.payment.chainId = chainId;
|
||||
d.payment.recipientAddress = doc.cart.recipientAddress!;
|
||||
d.payment.fiatAmount = String(amount);
|
||||
d.payment.fiatCurrency = doc.cart.currency || 'USD';
|
||||
d.payment.creatorDid = '';
|
||||
d.payment.creatorUsername = username;
|
||||
d.payment.status = 'pending';
|
||||
d.payment.paymentType = 'single';
|
||||
d.payment.maxPayments = 0;
|
||||
d.payment.paymentCount = 0;
|
||||
d.payment.enabledMethods = { card: true, wallet: true, encryptid: true };
|
||||
d.payment.linkedCartId = cartId;
|
||||
d.payment.createdAt = now;
|
||||
d.payment.updatedAt = now;
|
||||
d.payment.expiresAt = 0;
|
||||
});
|
||||
_syncServer!.setDoc(payDocId, payDoc);
|
||||
|
||||
const host = c.req.header("host") || "rspace.online";
|
||||
const payUrl = `/${space}/rcart/pay/${paymentId}`;
|
||||
|
||||
return c.json({ paymentId, payUrl, fullPayUrl: `https://${host}${payUrl}` }, 201);
|
||||
});
|
||||
|
||||
// ── Extension shortcut routes ──
|
||||
|
||||
// POST /api/cart/quick-add — Simplified endpoint for extension
|
||||
|
|
@ -1594,6 +1648,42 @@ routes.patch("/api/payments/:id/status", async (c) => {
|
|||
.catch((err) => console.error('[rcart] payment email failed:', err));
|
||||
}
|
||||
|
||||
// Auto-record contribution on linked shopping cart
|
||||
if (status === 'paid' && updated!.payment.linkedCartId) {
|
||||
const linkedCartId = updated!.payment.linkedCartId;
|
||||
const cartDocId = shoppingCartDocId(space, linkedCartId);
|
||||
const cartDoc = _syncServer!.getDoc<ShoppingCartDoc>(cartDocId);
|
||||
if (cartDoc) {
|
||||
const contribAmount = parseFloat(updated!.payment.amount) || 0;
|
||||
if (contribAmount > 0) {
|
||||
const contribId = crypto.randomUUID();
|
||||
const contribNow = Date.now();
|
||||
_syncServer!.changeDoc<ShoppingCartDoc>(cartDocId, 'auto-record payment contribution', (d) => {
|
||||
d.contributions[contribId] = {
|
||||
userId: null,
|
||||
username: updated!.payment.creatorUsername || 'Anonymous',
|
||||
amount: contribAmount,
|
||||
currency: d.cart.currency,
|
||||
paymentMethod: updated!.payment.paymentMethod || 'wallet',
|
||||
status: 'confirmed',
|
||||
txHash: updated!.payment.txHash || null,
|
||||
createdAt: contribNow,
|
||||
updatedAt: contribNow,
|
||||
};
|
||||
d.cart.fundedAmount = Math.round((d.cart.fundedAmount + contribAmount) * 100) / 100;
|
||||
d.cart.updatedAt = contribNow;
|
||||
d.events.push({
|
||||
type: 'contribution',
|
||||
actor: updated!.payment.creatorUsername || 'Anonymous',
|
||||
detail: `Paid $${contribAmount.toFixed(2)} via ${updated!.payment.paymentMethod || 'wallet'}`,
|
||||
timestamp: contribNow,
|
||||
});
|
||||
});
|
||||
reindexCart(space, linkedCartId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c.json(paymentToResponse(updated!.payment));
|
||||
});
|
||||
|
||||
|
|
@ -2022,6 +2112,7 @@ function paymentToResponse(p: PaymentRequestMeta) {
|
|||
paymentCount: p.paymentCount || 0,
|
||||
enabledMethods: p.enabledMethods || { card: true, wallet: true, encryptid: true },
|
||||
creatorUsername: p.creatorUsername || '',
|
||||
linkedCartId: p.linkedCartId || null,
|
||||
interval: p.interval || null,
|
||||
nextDueAt: p.nextDueAt ? new Date(p.nextDueAt).toISOString() : null,
|
||||
paymentHistory: (p.paymentHistory || []).map(h => ({
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ export interface ShoppingCartDoc {
|
|||
description: string;
|
||||
status: CartStatus;
|
||||
createdBy: string | null;
|
||||
recipientAddress: string | null;
|
||||
targetAmount: number;
|
||||
fundedAmount: number;
|
||||
currency: string;
|
||||
|
|
@ -246,6 +247,7 @@ export const shoppingCartSchema: DocSchema<ShoppingCartDoc> = {
|
|||
description: '',
|
||||
status: 'OPEN',
|
||||
createdBy: null,
|
||||
recipientAddress: null,
|
||||
targetAmount: 0,
|
||||
fundedAmount: 0,
|
||||
currency: 'USD',
|
||||
|
|
@ -322,6 +324,8 @@ export interface PaymentRequestMeta {
|
|||
nextDueAt: number;
|
||||
// Subscriber email (for payment reminders)
|
||||
subscriberEmail: string | null;
|
||||
// Linked shopping cart (for contribute-pay flow)
|
||||
linkedCartId: string | null;
|
||||
// Payment history (all individual payments)
|
||||
paymentHistory: PaymentRecord[];
|
||||
createdAt: number;
|
||||
|
|
@ -377,6 +381,7 @@ export const paymentRequestSchema: DocSchema<PaymentRequestDoc> = {
|
|||
interval: null,
|
||||
nextDueAt: 0,
|
||||
subscriberEmail: null,
|
||||
linkedCartId: null,
|
||||
paymentHistory: [],
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ routes.post("/api/collect", async (c) => {
|
|||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Data | rSpace`,
|
||||
moduleId: "rdata",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ routes.get("/api/health", (c) => {
|
|||
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const view = c.req.query("view");
|
||||
|
||||
if (view === "demo") {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ routes.get("/api/health", (c) => {
|
|||
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const view = c.req.query("view");
|
||||
|
||||
if (view === "demo") {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,6 +3,20 @@
|
|||
* Ported from rmaps-online/src/lib/sync.ts (simplified — no @/types dependency).
|
||||
*/
|
||||
|
||||
// ── Typed unions ──────────────────────────────────────────────
|
||||
|
||||
export type ParticipantStatus = "online" | "away" | "ghost" | "offline";
|
||||
export type WaypointType = "meeting" | "poi" | "parking" | "food" | "danger" | "custom";
|
||||
export type LocationSource = "gps" | "network" | "manual" | "ip" | "indoor";
|
||||
export type PrecisionLevel = "exact" | "building" | "area" | "approximate";
|
||||
|
||||
export interface PrivacySettings {
|
||||
precision: PrecisionLevel;
|
||||
ghostMode: boolean;
|
||||
}
|
||||
|
||||
// ── State interfaces ──────────────────────────────────────────
|
||||
|
||||
export interface RoomState {
|
||||
id: string;
|
||||
slug: string;
|
||||
|
|
@ -19,7 +33,7 @@ export interface ParticipantState {
|
|||
color: string;
|
||||
joinedAt: string;
|
||||
lastSeen: string;
|
||||
status: string;
|
||||
status: ParticipantStatus;
|
||||
location?: LocationState;
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +45,7 @@ export interface LocationState {
|
|||
heading?: number;
|
||||
speed?: number;
|
||||
timestamp: string;
|
||||
source: string;
|
||||
source: LocationSource;
|
||||
indoor?: { level: number; x: number; y: number; spaceName?: string };
|
||||
}
|
||||
|
||||
|
|
@ -44,14 +58,14 @@ export interface WaypointState {
|
|||
indoor?: { level: number; x: number; y: number };
|
||||
createdBy: string;
|
||||
createdAt: string;
|
||||
type: string;
|
||||
type: WaypointType;
|
||||
}
|
||||
|
||||
export type SyncMessage =
|
||||
| { type: "join"; participant: ParticipantState }
|
||||
| { type: "leave"; participantId: string }
|
||||
| { type: "location"; participantId: string; location: LocationState }
|
||||
| { type: "status"; participantId: string; status: string }
|
||||
| { type: "status"; participantId: string; status: ParticipantStatus }
|
||||
| { type: "waypoint_add"; waypoint: WaypointState }
|
||||
| { type: "waypoint_remove"; waypointId: string }
|
||||
| { type: "full_state"; state: RoomState }
|
||||
|
|
@ -263,7 +277,7 @@ export class RoomSync {
|
|||
}
|
||||
}
|
||||
|
||||
updateStatus(status: string): void {
|
||||
updateStatus(status: ParticipantStatus): void {
|
||||
if (this.state.participants[this.participantId]) {
|
||||
this.state.participants[this.participantId].status = status;
|
||||
this.state.participants[this.participantId].lastSeen = new Date().toISOString();
|
||||
|
|
|
|||
|
|
@ -86,22 +86,111 @@ routes.post("/api/push/request-location", async (c) => {
|
|||
// ── Proxy: routing (OSRM + c3nav) ──
|
||||
routes.post("/api/routing", async (c) => {
|
||||
const body = await c.req.json();
|
||||
const { from, to, mode = "walking" } = body;
|
||||
const { from, to, mode = "walking", accessibility, indoor } = body;
|
||||
|
||||
if (!from?.lat || !from?.lng || !to?.lat || !to?.lng) {
|
||||
return c.json({ error: "from and to with lat/lng required" }, 400);
|
||||
}
|
||||
|
||||
// Use OSRM for outdoor routing
|
||||
const segments: { type: string; coordinates: [number, number][]; distance: number; duration: number; steps?: any[] }[] = [];
|
||||
let totalDistance = 0;
|
||||
let estimatedTime = 0;
|
||||
|
||||
// ── Indoor routing via c3nav ──
|
||||
if (indoor?.event && (from.indoor || to.indoor)) {
|
||||
try {
|
||||
// If both are indoor, route fully indoors
|
||||
if (from.indoor && to.indoor) {
|
||||
const c3navRes = await fetch(`https://${indoor.event}.c3nav.de/api/v2/routing/route/`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "X-API-Key": "anonymous", Accept: "application/json", "User-Agent": "rMaps/1.0" },
|
||||
body: JSON.stringify({
|
||||
origin: { level: from.indoor.level, x: from.indoor.x, y: from.indoor.y },
|
||||
destination: { level: to.indoor.level, x: to.indoor.x, y: to.indoor.y },
|
||||
...(accessibility?.avoidStairs ? { options: { avoid_stairs: true } } : {}),
|
||||
...(accessibility?.wheelchair ? { options: { wheelchair: true } } : {}),
|
||||
}),
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (c3navRes.ok) {
|
||||
const data = await c3navRes.json();
|
||||
const coords: [number, number][] = data.path?.map((p: any) => [p.lng || p.x, p.lat || p.y]) || [];
|
||||
const dist = data.distance || 0;
|
||||
const dur = data.duration || Math.round(dist / 1.2);
|
||||
segments.push({ type: "indoor", coordinates: coords, distance: dist, duration: dur });
|
||||
totalDistance += dist;
|
||||
estimatedTime += dur;
|
||||
}
|
||||
} else {
|
||||
// Mixed indoor/outdoor: transition segment + outdoor OSRM
|
||||
const outdoorPoint = from.indoor ? to : from;
|
||||
const indoorPoint = from.indoor ? from : to;
|
||||
const transitionCoords: [number, number][] = [
|
||||
[indoorPoint.lng, indoorPoint.lat],
|
||||
[outdoorPoint.lng, outdoorPoint.lat],
|
||||
];
|
||||
segments.push({ type: "transition", coordinates: transitionCoords, distance: 50, duration: 30 });
|
||||
totalDistance += 50;
|
||||
estimatedTime += 30;
|
||||
|
||||
// OSRM for the outdoor portion
|
||||
const profile = mode === "driving" ? "car" : "foot";
|
||||
const osrmRes = await fetch(
|
||||
`https://router.project-osrm.org/route/v1/${profile}/${outdoorPoint.lng},${outdoorPoint.lat};${(from.indoor ? to : from).lng},${(from.indoor ? to : from).lat}?overview=full&geometries=geojson&steps=true`,
|
||||
{ signal: AbortSignal.timeout(10000) },
|
||||
);
|
||||
if (osrmRes.ok) {
|
||||
const osrmData = await osrmRes.json();
|
||||
const route = osrmData.routes?.[0];
|
||||
if (route) {
|
||||
segments.push({
|
||||
type: "outdoor",
|
||||
coordinates: route.geometry?.coordinates || [],
|
||||
distance: route.distance || 0,
|
||||
duration: route.duration || 0,
|
||||
steps: route.legs?.[0]?.steps,
|
||||
});
|
||||
totalDistance += route.distance || 0;
|
||||
estimatedTime += route.duration || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (segments.length > 0) {
|
||||
return c.json({
|
||||
success: true,
|
||||
route: { segments, totalDistance, estimatedTime: Math.round(estimatedTime) },
|
||||
});
|
||||
}
|
||||
} catch { /* fall through to outdoor-only routing */ }
|
||||
}
|
||||
|
||||
// ── Outdoor-only routing via OSRM ──
|
||||
const profile = mode === "driving" ? "car" : "foot";
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://router.project-osrm.org/route/v1/${profile}/${from.lng},${from.lat};${to.lng},${to.lat}?overview=full&geometries=geojson&steps=true`,
|
||||
{ signal: AbortSignal.timeout(10000) }
|
||||
{ signal: AbortSignal.timeout(10000) },
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
return c.json(data);
|
||||
const route = data.routes?.[0];
|
||||
if (route) {
|
||||
return c.json({
|
||||
success: true,
|
||||
route: {
|
||||
segments: [{
|
||||
type: "outdoor",
|
||||
coordinates: route.geometry?.coordinates || [],
|
||||
distance: route.distance || 0,
|
||||
duration: route.duration || 0,
|
||||
steps: route.legs?.[0]?.steps,
|
||||
}],
|
||||
totalDistance: route.distance || 0,
|
||||
estimatedTime: Math.round(route.duration || 0),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
return c.json({ error: "Routing failed" }, 502);
|
||||
|
|
@ -134,7 +223,7 @@ routes.get("/api/c3nav/:event", async (c) => {
|
|||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Maps | rSpace`,
|
||||
moduleId: "rmaps",
|
||||
|
|
@ -149,7 +238,7 @@ routes.get("/", (c) => {
|
|||
// Room-specific page
|
||||
routes.get("/:room", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const room = c.req.param("room");
|
||||
return c.html(renderShell({
|
||||
title: `${room} — Maps | rSpace`,
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ const CACHE_TTL = 60_000;
|
|||
// ── API: Health ──
|
||||
routes.get("/api/health", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = getTokenForSpace(dataSpace);
|
||||
return c.json({ ok: true, module: "network", space, twentyConfigured: !!token });
|
||||
});
|
||||
|
|
@ -75,7 +75,7 @@ routes.get("/api/health", (c) => {
|
|||
// ── API: Info ──
|
||||
routes.get("/api/info", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = getTokenForSpace(dataSpace);
|
||||
return c.json({
|
||||
module: "network",
|
||||
|
|
@ -90,7 +90,7 @@ routes.get("/api/info", (c) => {
|
|||
// ── API: People ──
|
||||
routes.get("/api/people", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = getTokenForSpace(dataSpace);
|
||||
const data = await twentyQuery(`{
|
||||
people(first: 200) {
|
||||
|
|
@ -116,7 +116,7 @@ routes.get("/api/people", async (c) => {
|
|||
// ── API: Companies ──
|
||||
routes.get("/api/companies", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = getTokenForSpace(dataSpace);
|
||||
const data = await twentyQuery(`{
|
||||
companies(first: 200) {
|
||||
|
|
@ -194,7 +194,7 @@ routes.get("/api/delegations", async (c) => {
|
|||
// ── API: Graph — transform entities to node/edge format ──
|
||||
routes.get("/api/graph", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = getTokenForSpace(dataSpace);
|
||||
|
||||
// Check per-space cache (keyed by space + trust params)
|
||||
|
|
@ -540,7 +540,7 @@ routes.get("/api/workspaces", (c) => {
|
|||
// ── API: Opportunities ──
|
||||
routes.get("/api/opportunities", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = getTokenForSpace(dataSpace);
|
||||
const data = await twentyQuery(`{
|
||||
opportunities(first: 200) {
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ function extractPlainText(content: string, format?: string): string {
|
|||
// GET /api/notebooks — list notebooks
|
||||
routes.get("/api/notebooks", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
||||
const notebooks = listNotebooks(dataSpace).map(({ doc }) => notebookToRest(doc));
|
||||
notebooks.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime());
|
||||
|
|
@ -260,7 +260,7 @@ routes.get("/api/notebooks", async (c) => {
|
|||
// POST /api/notebooks — create notebook
|
||||
routes.post("/api/notebooks", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
|
|
@ -292,7 +292,7 @@ routes.post("/api/notebooks", async (c) => {
|
|||
// GET /api/notebooks/:id — notebook detail with notes
|
||||
routes.get("/api/notebooks/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const docId = notebookDocId(dataSpace, id);
|
||||
|
|
@ -314,7 +314,7 @@ routes.get("/api/notebooks/:id", async (c) => {
|
|||
// PUT /api/notebooks/:id — update notebook
|
||||
routes.put("/api/notebooks/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
|
|
@ -349,7 +349,7 @@ routes.put("/api/notebooks/:id", async (c) => {
|
|||
// DELETE /api/notebooks/:id
|
||||
routes.delete("/api/notebooks/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const docId = notebookDocId(dataSpace, id);
|
||||
|
|
@ -376,7 +376,7 @@ routes.delete("/api/notebooks/:id", async (c) => {
|
|||
// GET /api/notes — list all notes
|
||||
routes.get("/api/notes", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const { notebook_id, type, q, limit = "50", offset = "0" } = c.req.query();
|
||||
|
||||
let allNotes: ReturnType<typeof noteToRest>[] = [];
|
||||
|
|
@ -412,7 +412,7 @@ routes.get("/api/notes", async (c) => {
|
|||
// POST /api/notes — create note
|
||||
routes.post("/api/notes", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
|
|
@ -464,7 +464,7 @@ routes.post("/api/notes", async (c) => {
|
|||
// GET /api/notes/:id — note detail
|
||||
routes.get("/api/notes/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const found = findNote(dataSpace, id);
|
||||
|
|
@ -476,7 +476,7 @@ routes.get("/api/notes/:id", async (c) => {
|
|||
// PUT /api/notes/:id — update note
|
||||
routes.put("/api/notes/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
const { title, content, content_format, type, url, language, is_pinned, sort_order } = body;
|
||||
|
|
@ -517,7 +517,7 @@ routes.put("/api/notes/:id", async (c) => {
|
|||
// DELETE /api/notes/:id
|
||||
routes.delete("/api/notes/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const found = findNote(dataSpace, id);
|
||||
|
|
@ -601,7 +601,7 @@ function getConnectionDoc(space: string): ConnectionsDoc | null {
|
|||
// POST /api/import/upload — ZIP upload for Logseq/Obsidian
|
||||
routes.post("/api/import/upload", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
|
@ -653,7 +653,7 @@ routes.post("/api/import/upload", async (c) => {
|
|||
// POST /api/import/notion — Import selected Notion pages
|
||||
routes.post("/api/import/notion", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
|
@ -699,7 +699,7 @@ routes.post("/api/import/notion", async (c) => {
|
|||
// POST /api/import/google-docs — Import selected Google Docs
|
||||
routes.post("/api/import/google-docs", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
|
@ -744,7 +744,7 @@ routes.post("/api/import/google-docs", async (c) => {
|
|||
// GET /api/import/notion/pages — Browse Notion pages for selection
|
||||
routes.get("/api/import/notion/pages", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const conn = getConnectionDoc(dataSpace);
|
||||
if (!conn?.notion?.accessToken) {
|
||||
return c.json({ error: "Notion not connected" }, 400);
|
||||
|
|
@ -787,7 +787,7 @@ routes.get("/api/import/notion/pages", async (c) => {
|
|||
// GET /api/import/google-docs/list — Browse Google Docs for selection
|
||||
routes.get("/api/import/google-docs/list", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const conn = getConnectionDoc(dataSpace);
|
||||
if (!conn?.google?.accessToken) {
|
||||
return c.json({ error: "Google not connected" }, 400);
|
||||
|
|
@ -818,7 +818,7 @@ routes.get("/api/import/google-docs/list", async (c) => {
|
|||
// GET /api/export/obsidian — Download Obsidian-format ZIP
|
||||
routes.get("/api/export/obsidian", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const notebookId = c.req.query("notebookId");
|
||||
if (!notebookId) return c.json({ error: "notebookId is required" }, 400);
|
||||
|
||||
|
|
@ -841,7 +841,7 @@ routes.get("/api/export/obsidian", async (c) => {
|
|||
// GET /api/export/logseq — Download Logseq-format ZIP
|
||||
routes.get("/api/export/logseq", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const notebookId = c.req.query("notebookId");
|
||||
if (!notebookId) return c.json({ error: "notebookId is required" }, 400);
|
||||
|
||||
|
|
@ -864,7 +864,7 @@ routes.get("/api/export/logseq", async (c) => {
|
|||
// GET /api/export/markdown — Download universal Markdown ZIP
|
||||
routes.get("/api/export/markdown", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const notebookId = c.req.query("notebookId");
|
||||
const noteIds = c.req.query("noteIds");
|
||||
|
||||
|
|
@ -904,7 +904,7 @@ routes.get("/api/export/markdown", async (c) => {
|
|||
// POST /api/export/notion — Push notes to Notion
|
||||
routes.post("/api/export/notion", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
|
@ -944,7 +944,7 @@ routes.post("/api/export/notion", async (c) => {
|
|||
// POST /api/export/google-docs — Push notes to Google Docs
|
||||
routes.post("/api/export/google-docs", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
|
@ -984,7 +984,7 @@ routes.post("/api/export/google-docs", async (c) => {
|
|||
// GET /api/connections — Status of all integrations
|
||||
routes.get("/api/connections", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const conn = getConnectionDoc(dataSpace);
|
||||
|
||||
return c.json({
|
||||
|
|
@ -1315,7 +1315,7 @@ routes.get("/voice", (c) => {
|
|||
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Notes | rSpace`,
|
||||
moduleId: "rnotes",
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ routes.get("/api/assets/:id/original", async (c) => {
|
|||
// ── Embedded Immich UI ──
|
||||
routes.get("/album", (c) => {
|
||||
const spaceSlug = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
return c.html(renderExternalAppShell({
|
||||
title: `${spaceSlug} — Immich | rSpace`,
|
||||
moduleId: "rphotos",
|
||||
|
|
@ -125,7 +125,7 @@ routes.get("/album", (c) => {
|
|||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const spaceSlug = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
return c.html(renderShell({
|
||||
title: `${spaceSlug} — Photos | rSpace`,
|
||||
moduleId: "rphotos",
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ routes.get("/zine", (c) => {
|
|||
// ── Page: Editor ──
|
||||
routes.get("/", (c) => {
|
||||
const spaceSlug = c.req.param("space") || "personal";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
return c.html(renderShell({
|
||||
title: `${spaceSlug} — rPubs Editor | rSpace`,
|
||||
moduleId: "rpubs",
|
||||
|
|
|
|||
|
|
@ -799,7 +799,7 @@ function seedDefaultJobs(space: string) {
|
|||
// GET / — serve schedule UI
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(
|
||||
renderShell({
|
||||
title: `${space} — Schedule | rSpace`,
|
||||
|
|
@ -817,7 +817,7 @@ routes.get("/", (c) => {
|
|||
// GET /api/jobs — list all jobs
|
||||
routes.get("/api/jobs", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const doc = ensureDoc(dataSpace);
|
||||
const jobs = Object.values(doc.jobs).map((j) => ({
|
||||
...j,
|
||||
|
|
@ -830,7 +830,7 @@ routes.get("/api/jobs", (c) => {
|
|||
// POST /api/jobs — create a new job
|
||||
routes.post("/api/jobs", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const body = await c.req.json();
|
||||
|
||||
const { name, description, cronExpression, timezone, actionType, actionConfig, enabled } = body;
|
||||
|
|
@ -878,7 +878,7 @@ routes.post("/api/jobs", async (c) => {
|
|||
// GET /api/jobs/:id
|
||||
routes.get("/api/jobs/:id", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
||||
|
|
@ -890,7 +890,7 @@ routes.get("/api/jobs/:id", (c) => {
|
|||
// PUT /api/jobs/:id — update a job
|
||||
routes.put("/api/jobs/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
|
||||
|
|
@ -933,7 +933,7 @@ routes.put("/api/jobs/:id", async (c) => {
|
|||
// DELETE /api/jobs/:id
|
||||
routes.delete("/api/jobs/:id", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const docId = scheduleDocId(dataSpace);
|
||||
|
|
@ -950,7 +950,7 @@ routes.delete("/api/jobs/:id", (c) => {
|
|||
// POST /api/jobs/:id/run — manually trigger a job
|
||||
routes.post("/api/jobs/:id/run", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const docId = scheduleDocId(dataSpace);
|
||||
|
|
@ -997,7 +997,7 @@ routes.post("/api/jobs/:id/run", async (c) => {
|
|||
// GET /api/log — execution log
|
||||
routes.get("/api/log", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const doc = ensureDoc(dataSpace);
|
||||
const log = [...doc.log].reverse(); // newest first
|
||||
return c.json({ count: log.length, results: log });
|
||||
|
|
@ -1006,7 +1006,7 @@ routes.get("/api/log", (c) => {
|
|||
// GET /api/log/:jobId — execution log filtered by job
|
||||
routes.get("/api/log/:jobId", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const jobId = c.req.param("jobId");
|
||||
const doc = ensureDoc(dataSpace);
|
||||
const log = doc.log.filter((e) => e.jobId === jobId).reverse();
|
||||
|
|
@ -1156,7 +1156,7 @@ async function executeReminderEmail(
|
|||
// GET /api/reminders — list reminders
|
||||
routes.get("/api/reminders", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
||||
let reminders = Object.values(doc.reminders);
|
||||
|
|
@ -1185,7 +1185,7 @@ routes.get("/api/reminders", (c) => {
|
|||
// POST /api/reminders — create a reminder
|
||||
routes.post("/api/reminders", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const body = await c.req.json();
|
||||
|
||||
const { title, description, remindAt, allDay, timezone, notifyEmail, syncToCalendar, cronExpression } = body;
|
||||
|
|
@ -1239,7 +1239,7 @@ routes.post("/api/reminders", async (c) => {
|
|||
// GET /api/reminders/:id — get single reminder
|
||||
routes.get("/api/reminders/:id", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
||||
|
|
@ -1251,7 +1251,7 @@ routes.get("/api/reminders/:id", (c) => {
|
|||
// PUT /api/reminders/:id — update a reminder
|
||||
routes.put("/api/reminders/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
|
||||
|
|
@ -1279,7 +1279,7 @@ routes.put("/api/reminders/:id", async (c) => {
|
|||
// DELETE /api/reminders/:id — delete (cascades to calendar)
|
||||
routes.delete("/api/reminders/:id", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const docId = scheduleDocId(dataSpace);
|
||||
|
|
@ -1302,7 +1302,7 @@ routes.delete("/api/reminders/:id", (c) => {
|
|||
// POST /api/reminders/:id/complete — mark completed
|
||||
routes.post("/api/reminders/:id/complete", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const docId = scheduleDocId(dataSpace);
|
||||
|
|
@ -1323,7 +1323,7 @@ routes.post("/api/reminders/:id/complete", (c) => {
|
|||
// POST /api/reminders/:id/snooze — reschedule to a new date
|
||||
routes.post("/api/reminders/:id/snooze", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
|
||||
|
|
@ -1384,7 +1384,7 @@ routes.get("/reminders", (c) => {
|
|||
|
||||
routes.get("/api/workflows", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const doc = ensureDoc(dataSpace);
|
||||
// Ensure workflows field exists on older docs
|
||||
const workflows = Object.values(doc.workflows || {});
|
||||
|
|
@ -1394,7 +1394,7 @@ routes.get("/api/workflows", (c) => {
|
|||
|
||||
routes.post("/api/workflows", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const body = await c.req.json();
|
||||
|
||||
const docId = scheduleDocId(dataSpace);
|
||||
|
|
@ -1426,7 +1426,7 @@ routes.post("/api/workflows", async (c) => {
|
|||
|
||||
routes.get("/api/workflows/:id", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
||||
|
|
@ -1437,7 +1437,7 @@ routes.get("/api/workflows/:id", (c) => {
|
|||
|
||||
routes.put("/api/workflows/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
|
||||
|
|
@ -1468,7 +1468,7 @@ routes.put("/api/workflows/:id", async (c) => {
|
|||
|
||||
routes.delete("/api/workflows/:id", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const docId = scheduleDocId(dataSpace);
|
||||
|
|
@ -1919,7 +1919,7 @@ function appendWorkflowLog(
|
|||
// POST /api/workflows/:id/run — manual execute
|
||||
routes.post("/api/workflows/:id/run", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const docId = scheduleDocId(dataSpace);
|
||||
|
|
@ -1947,7 +1947,7 @@ routes.post("/api/workflows/:id/run", async (c) => {
|
|||
// POST /api/workflows/webhook/:hookId — external webhook trigger
|
||||
routes.post("/api/workflows/webhook/:hookId", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const hookId = c.req.param("hookId");
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
|
@ -1981,7 +1981,7 @@ routes.post("/api/workflows/webhook/:hookId", async (c) => {
|
|||
// GET /api/workflows/log — workflow execution log
|
||||
routes.get("/api/workflows/log", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const doc = ensureDoc(dataSpace);
|
||||
const log = [...(doc.workflowLog || [])].reverse(); // newest first
|
||||
return c.json({ count: log.length, results: log });
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ routes.get("/api/feed", (c) =>
|
|||
|
||||
routes.post("/api/threads/:id/image", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
if (!id || id.includes("..") || id.includes("/")) return c.json({ error: "Invalid ID" }, 400);
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ routes.post("/api/threads/:id/image", async (c) => {
|
|||
|
||||
routes.post("/api/threads/:id/upload-image", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
if (!id || id.includes("..") || id.includes("/")) return c.json({ error: "Invalid ID" }, 400);
|
||||
|
||||
|
|
@ -277,7 +277,7 @@ routes.post("/api/threads/:id/upload-image", async (c) => {
|
|||
|
||||
routes.post("/api/threads/:id/tweet/:index/upload-image", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const index = c.req.param("index");
|
||||
if (!id || id.includes("..") || id.includes("/")) return c.json({ error: "Invalid ID" }, 400);
|
||||
|
|
@ -318,7 +318,7 @@ routes.post("/api/threads/:id/tweet/:index/upload-image", async (c) => {
|
|||
|
||||
routes.post("/api/threads/:id/tweet/:index/image", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const index = c.req.param("index");
|
||||
if (!id || id.includes("..") || id.includes("/")) return c.json({ error: "Invalid ID" }, 400);
|
||||
|
|
@ -374,7 +374,7 @@ routes.post("/api/threads/:id/tweet/:index/image", async (c) => {
|
|||
|
||||
routes.delete("/api/threads/:id/tweet/:index/image", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const index = c.req.param("index");
|
||||
if (!id || id.includes("..") || id.includes("/")) return c.json({ error: "Invalid ID" }, 400);
|
||||
|
|
@ -403,7 +403,7 @@ routes.delete("/api/threads/:id/tweet/:index/image", async (c) => {
|
|||
|
||||
routes.delete("/api/threads/:id/images", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
if (!id || id.includes("..") || id.includes("/")) return c.json({ error: "Invalid ID" }, 400);
|
||||
|
||||
|
|
@ -640,7 +640,7 @@ Rules:
|
|||
|
||||
routes.get("/api/campaign-workflows", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const doc = ensureDoc(dataSpace);
|
||||
const workflows = Object.values(doc.campaignWorkflows || {});
|
||||
workflows.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
|
@ -649,7 +649,7 @@ routes.get("/api/campaign-workflows", (c) => {
|
|||
|
||||
routes.post("/api/campaign-workflows", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const body = await c.req.json();
|
||||
|
||||
const docId = socialsDocId(dataSpace);
|
||||
|
|
@ -681,7 +681,7 @@ routes.post("/api/campaign-workflows", async (c) => {
|
|||
|
||||
routes.get("/api/campaign-workflows/:id", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
||||
|
|
@ -692,7 +692,7 @@ routes.get("/api/campaign-workflows/:id", (c) => {
|
|||
|
||||
routes.put("/api/campaign-workflows/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
|
||||
|
|
@ -722,7 +722,7 @@ routes.put("/api/campaign-workflows/:id", async (c) => {
|
|||
|
||||
routes.delete("/api/campaign-workflows/:id", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const docId = socialsDocId(dataSpace);
|
||||
|
|
@ -739,7 +739,7 @@ routes.delete("/api/campaign-workflows/:id", (c) => {
|
|||
// POST /api/campaign-workflows/:id/run — manual execute (stub)
|
||||
routes.post("/api/campaign-workflows/:id/run", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
|
@ -777,7 +777,7 @@ routes.post("/api/campaign-workflows/:id/run", async (c) => {
|
|||
routes.post("/api/campaign-workflows/webhook/:hookId", async (c) => {
|
||||
const hookId = c.req.param("hookId");
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
// Find workflow containing a webhook-trigger node with this hookId
|
||||
|
|
@ -831,7 +831,7 @@ function topologicalSortCampaign(nodes: CampaignWorkflowNode[], edges: CampaignW
|
|||
|
||||
routes.get("/campaign", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `Campaign — rSocials | rSpace`,
|
||||
moduleId: "rsocials",
|
||||
|
|
@ -846,7 +846,7 @@ routes.get("/campaign", (c) => {
|
|||
|
||||
routes.get("/thread/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
if (!id || id.includes("..") || id.includes("/")) return c.text("Not found", 404);
|
||||
|
||||
|
|
@ -889,7 +889,7 @@ routes.get("/thread/:id", async (c) => {
|
|||
|
||||
routes.get("/thread-editor/:id/edit", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const id = c.req.param("id");
|
||||
if (!id || id.includes("..") || id.includes("/")) return c.text("Not found", 404);
|
||||
|
||||
|
|
@ -912,7 +912,7 @@ routes.get("/thread-editor/:id/edit", async (c) => {
|
|||
|
||||
routes.get("/thread-editor", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `Thread Editor — rSocials | rSpace`,
|
||||
moduleId: "rsocials",
|
||||
|
|
@ -927,7 +927,7 @@ routes.get("/thread-editor", (c) => {
|
|||
|
||||
routes.get("/threads", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `Threads — rSocials | rSpace`,
|
||||
moduleId: "rsocials",
|
||||
|
|
@ -999,7 +999,7 @@ const POSTIZ_URL = process.env.POSTIZ_URL || "https://demo.rsocials.online";
|
|||
|
||||
routes.get("/scheduler", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderExternalAppShell({
|
||||
title: `Post Scheduler — rSocials | rSpace`,
|
||||
moduleId: "rsocials",
|
||||
|
|
@ -1039,7 +1039,7 @@ routes.get("/newsletter-list", async (c) => {
|
|||
|
||||
routes.get("/feed", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const isDemo = space === "demo";
|
||||
const body = isDemo ? renderDemoFeedHTML() : renderLanding();
|
||||
const styles = isDemo
|
||||
|
|
@ -1058,7 +1058,7 @@ routes.get("/feed", (c) => {
|
|||
|
||||
routes.get("/landing", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `${space} — rSocials | rSpace`,
|
||||
moduleId: "rsocials",
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ const routes = new Hono();
|
|||
// ── API: List splats ──
|
||||
routes.get("/api/splats", async (c) => {
|
||||
const spaceSlug = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
const tag = c.req.query("tag");
|
||||
const limit = Math.min(parseInt(c.req.query("limit") || "50"), 100);
|
||||
const offset = parseInt(c.req.query("offset") || "0");
|
||||
|
|
@ -231,7 +231,7 @@ routes.get("/api/splats", async (c) => {
|
|||
// ── API: Get splat details ──
|
||||
routes.get("/api/splats/:id", async (c) => {
|
||||
const spaceSlug = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
|
@ -256,7 +256,7 @@ routes.get("/api/splats/:id", async (c) => {
|
|||
// Matches both /api/splats/:id/file and /api/splats/:id/:filename (e.g. rainbow-sphere.splat)
|
||||
routes.get("/api/splats/:id/:filename", async (c) => {
|
||||
const spaceSlug = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
|
@ -310,7 +310,7 @@ routes.post("/api/splats", async (c) => {
|
|||
}
|
||||
|
||||
const spaceSlug = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
const formData = await c.req.formData();
|
||||
const file = formData.get("file") as File | null;
|
||||
const title = (formData.get("title") as string || "").trim();
|
||||
|
|
@ -422,7 +422,7 @@ routes.post("/api/splats/from-media", async (c) => {
|
|||
}
|
||||
|
||||
const spaceSlug = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
const formData = await c.req.formData();
|
||||
const title = (formData.get("title") as string || "").trim();
|
||||
const description = (formData.get("description") as string || "").trim() || null;
|
||||
|
|
@ -554,7 +554,7 @@ routes.delete("/api/splats/:id", async (c) => {
|
|||
}
|
||||
|
||||
const spaceSlug = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
|
@ -580,7 +580,7 @@ routes.delete("/api/splats/:id", async (c) => {
|
|||
// ── Page: Gallery ──
|
||||
routes.get("/", async (c) => {
|
||||
const spaceSlug = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
||||
|
|
@ -619,7 +619,7 @@ routes.get("/", async (c) => {
|
|||
// ── Page: Viewer ──
|
||||
routes.get("/view/:id", async (c) => {
|
||||
const spaceSlug = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || spaceSlug;
|
||||
const dataSpace = c.get("effectiveSpace") || spaceSlug;
|
||||
const id = c.req.param("id");
|
||||
|
||||
const doc = ensureDoc(dataSpace);
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ routes.get("/api/artifact/:id", async (c) => {
|
|||
// ── Page route: swag designer ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `Swag Designer | rSpace`,
|
||||
moduleId: "rswag",
|
||||
|
|
|
|||
|
|
@ -455,7 +455,7 @@ routes.get("/api/spaces/:slug/activity", async (c) => {
|
|||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Tasks | rSpace`,
|
||||
moduleId: "rtasks",
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ const routes = new Hono();
|
|||
// GET /api/trips — list trips
|
||||
routes.get("/api/trips", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const docIds = listTripDocIds(dataSpace);
|
||||
|
||||
const rows = docIds.map((docId) => {
|
||||
|
|
@ -103,7 +103,7 @@ routes.post("/api/trips", async (c) => {
|
|||
if (!title?.trim()) return c.json({ error: "Title required" }, 400);
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const tripId = newId();
|
||||
const slug = title.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
||||
const now = Date.now();
|
||||
|
|
@ -142,7 +142,7 @@ routes.post("/api/trips", async (c) => {
|
|||
// GET /api/trips/:id — trip detail with all sub-resources
|
||||
routes.get("/api/trips/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const tripId = c.req.param("id");
|
||||
const docId = tripDocId(dataSpace, tripId);
|
||||
const doc = _syncServer!.getDoc<TripDoc>(docId);
|
||||
|
|
@ -166,7 +166,7 @@ routes.get("/api/trips/:id", async (c) => {
|
|||
// PUT /api/trips/:id — update trip
|
||||
routes.put("/api/trips/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const tripId = c.req.param("id");
|
||||
const docId = tripDocId(dataSpace, tripId);
|
||||
const doc = _syncServer!.getDoc<TripDoc>(docId);
|
||||
|
|
@ -202,7 +202,7 @@ routes.post("/api/trips/:id/destinations", async (c) => {
|
|||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const tripId = c.req.param("id");
|
||||
ensureDoc(dataSpace, tripId);
|
||||
const docId = tripDocId(dataSpace, tripId);
|
||||
|
|
@ -239,7 +239,7 @@ routes.post("/api/trips/:id/itinerary", async (c) => {
|
|||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const tripId = c.req.param("id");
|
||||
ensureDoc(dataSpace, tripId);
|
||||
const docId = tripDocId(dataSpace, tripId);
|
||||
|
|
@ -276,7 +276,7 @@ routes.post("/api/trips/:id/bookings", async (c) => {
|
|||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const tripId = c.req.param("id");
|
||||
ensureDoc(dataSpace, tripId);
|
||||
const docId = tripDocId(dataSpace, tripId);
|
||||
|
|
@ -314,7 +314,7 @@ routes.post("/api/trips/:id/expenses", async (c) => {
|
|||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const tripId = c.req.param("id");
|
||||
ensureDoc(dataSpace, tripId);
|
||||
const docId = tripDocId(dataSpace, tripId);
|
||||
|
|
@ -346,7 +346,7 @@ routes.post("/api/trips/:id/expenses", async (c) => {
|
|||
|
||||
routes.get("/api/trips/:id/packing", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const tripId = c.req.param("id");
|
||||
const docId = tripDocId(dataSpace, tripId);
|
||||
const doc = _syncServer!.getDoc<TripDoc>(docId);
|
||||
|
|
@ -365,7 +365,7 @@ routes.post("/api/trips/:id/packing", async (c) => {
|
|||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const tripId = c.req.param("id");
|
||||
ensureDoc(dataSpace, tripId);
|
||||
const docId = tripDocId(dataSpace, tripId);
|
||||
|
|
@ -394,7 +394,7 @@ routes.post("/api/trips/:id/packing", async (c) => {
|
|||
|
||||
routes.patch("/api/packing/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const packingId = c.req.param("id");
|
||||
|
||||
// Find the trip doc containing this packing item
|
||||
|
|
@ -434,7 +434,7 @@ routes.post("/api/route", async (c) => {
|
|||
// ── Route planner page ──
|
||||
routes.get("/routes", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Route Planner | rTrips`,
|
||||
moduleId: "rtrips",
|
||||
|
|
@ -586,7 +586,7 @@ routes.get("/demo", (c) => {
|
|||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Trips | rSpace`,
|
||||
moduleId: "rtrips",
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ routes.get("/api/health", (c) => c.json({ ok: true }));
|
|||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Tube | rSpace`,
|
||||
moduleId: "rtube",
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ const CHAIN_COLORS: Record<string, string> = {
|
|||
"324": "#8c8dfc",
|
||||
"11155111": "#f59e0b",
|
||||
"84532": "#f59e0b",
|
||||
"421614": "#9ca3af",
|
||||
"11155420": "#ff6680",
|
||||
"80002": "#a855f7",
|
||||
"43113": "#fb923c",
|
||||
"97": "#fbbf24",
|
||||
};
|
||||
|
||||
const CHAIN_NAMES: Record<string, string> = {
|
||||
|
|
|
|||
|
|
@ -121,9 +121,14 @@ const CHAIN_MAP: Record<string, { name: string; prefix: string }> = {
|
|||
"324": { name: "zkSync", prefix: "zksync" },
|
||||
"11155111": { name: "Sepolia", prefix: "sep" },
|
||||
"84532": { name: "Base Sepolia", prefix: "basesep" },
|
||||
"421614": { name: "Arbitrum Sepolia", prefix: "arbsep" },
|
||||
"11155420": { name: "Optimism Sepolia", prefix: "optsep" },
|
||||
"80002": { name: "Polygon Amoy", prefix: "polyamoy" },
|
||||
"43113": { name: "Avalanche Fuji", prefix: "avaxfuji" },
|
||||
"97": { name: "BSC Testnet", prefix: "bsctest" },
|
||||
};
|
||||
|
||||
const TESTNET_CHAIN_IDS = new Set(["11155111", "84532"]);
|
||||
const TESTNET_CHAIN_IDS = new Set(["11155111", "84532", "421614", "11155420", "80002", "43113", "97"]);
|
||||
|
||||
function getChains(includeTestnets: boolean): [string, { name: string; prefix: string }][] {
|
||||
return Object.entries(CHAIN_MAP).filter(([id]) => includeTestnets || !TESTNET_CHAIN_IDS.has(id));
|
||||
|
|
@ -151,6 +156,11 @@ const DEFAULT_RPC_URLS: Record<string, string> = {
|
|||
"324": "https://mainnet.era.zksync.io",
|
||||
"11155111": "https://rpc.sepolia.org",
|
||||
"84532": "https://sepolia.base.org",
|
||||
"421614": "https://sepolia-rollup.arbitrum.io/rpc",
|
||||
"11155420": "https://sepolia.optimism.io",
|
||||
"80002": "https://rpc-amoy.polygon.technology",
|
||||
"43113": "https://api.avax-test.network/ext/bc/C/rpc",
|
||||
"97": "https://data-seed-prebsc-1-s1.binance.org:8545",
|
||||
};
|
||||
|
||||
// Chain ID → env var name fragment + Alchemy subdomain (for auto-construct)
|
||||
|
|
@ -165,8 +175,13 @@ const CHAIN_ENV_NAMES: Record<string, { envName: string; alchemySlug?: string }>
|
|||
"43114": { envName: "AVALANCHE" },
|
||||
"56": { envName: "BSC" },
|
||||
"324": { envName: "ZKSYNC" },
|
||||
"11155111": { envName: "SEPOLIA" },
|
||||
"84532": { envName: "BASE_SEPOLIA" },
|
||||
"11155111": { envName: "SEPOLIA", alchemySlug: "eth-sepolia" },
|
||||
"84532": { envName: "BASE_SEPOLIA", alchemySlug: "base-sepolia" },
|
||||
"421614": { envName: "ARB_SEPOLIA", alchemySlug: "arb-sepolia" },
|
||||
"11155420": { envName: "OPT_SEPOLIA", alchemySlug: "opt-sepolia" },
|
||||
"80002": { envName: "POLYGON_AMOY", alchemySlug: "polygon-amoy" },
|
||||
"43113": { envName: "AVAX_FUJI" },
|
||||
"97": { envName: "BSC_TESTNET" },
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -204,6 +219,11 @@ const NATIVE_TOKENS: Record<string, { name: string; symbol: string; decimals: nu
|
|||
"324": { name: "Ether", symbol: "ETH", decimals: 18 },
|
||||
"11155111": { name: "Sepolia ETH", symbol: "ETH", decimals: 18 },
|
||||
"84532": { name: "Base Sepolia ETH", symbol: "ETH", decimals: 18 },
|
||||
"421614": { name: "Arb Sepolia ETH", symbol: "ETH", decimals: 18 },
|
||||
"11155420": { name: "OP Sepolia ETH", symbol: "ETH", decimals: 18 },
|
||||
"80002": { name: "Amoy POL", symbol: "POL", decimals: 18 },
|
||||
"43113": { name: "Fuji AVAX", symbol: "AVAX", decimals: 18 },
|
||||
"97": { name: "Test BNB", symbol: "tBNB", decimals: 18 },
|
||||
};
|
||||
|
||||
async function rpcCall(rpcUrl: string, method: string, params: any[]): Promise<any> {
|
||||
|
|
@ -440,6 +460,17 @@ const POPULAR_TOKENS: Record<string, Array<{ address: string; name: string; symb
|
|||
"84532": [
|
||||
{ address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", name: "USD Coin", symbol: "USDC", decimals: 6 },
|
||||
],
|
||||
"421614": [
|
||||
{ address: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", name: "USD Coin", symbol: "USDC", decimals: 6 },
|
||||
{ address: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73", name: "Wrapped Ether", symbol: "WETH", decimals: 18 },
|
||||
],
|
||||
"11155420": [
|
||||
{ address: "0x5fd84259d66Cd46123540766Be93DFE6D43130D7", name: "USD Coin", symbol: "USDC", decimals: 6 },
|
||||
{ address: "0x4200000000000000000000000000000000000006", name: "Wrapped Ether", symbol: "WETH", decimals: 18 },
|
||||
],
|
||||
"80002": [
|
||||
{ address: "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582", name: "USD Coin", symbol: "USDC", decimals: 6 },
|
||||
],
|
||||
};
|
||||
|
||||
// ERC-20 balanceOf(address) — selector 0x70a08231
|
||||
|
|
|
|||
|
|
@ -1664,7 +1664,7 @@ for (const mod of getAllModules()) {
|
|||
// Resolve effective data space (global vs space-scoped)
|
||||
const overrides = doc?.meta?.moduleScopeOverrides ?? null;
|
||||
const effectiveSpace = resolveDataSpace(mod.id, space, overrides);
|
||||
c.set("effectiveSpace" as any, effectiveSpace);
|
||||
c.set("effectiveSpace", effectiveSpace);
|
||||
|
||||
// Resolve caller's role for write-method blocking
|
||||
const method = c.req.method;
|
||||
|
|
@ -1676,8 +1676,8 @@ for (const mod of getAllModules()) {
|
|||
}
|
||||
const resolved = await resolveCallerRole(space, claims);
|
||||
if (resolved) {
|
||||
c.set("spaceRole" as any, resolved.role);
|
||||
c.set("isOwner" as any, resolved.isOwner);
|
||||
c.set("spaceRole", resolved.role);
|
||||
c.set("isOwner", resolved.isOwner);
|
||||
if (resolved.role === "viewer") {
|
||||
return c.json({ error: "Write access required" }, 403);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import 'hono';
|
||||
|
||||
declare module 'hono' {
|
||||
interface ContextVariableMap {
|
||||
effectiveSpace: string;
|
||||
spaceRole: string;
|
||||
isOwner: boolean;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue