Merge branch 'dev'
This commit is contained in:
commit
ec67bf46a5
|
|
@ -17,7 +17,7 @@ import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
|||
import { renderLanding } from "./landing";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import { calendarSchema, calendarDocId } from './schemas';
|
||||
import type { CalendarDoc, CalendarEvent, CalendarSource } from './schemas';
|
||||
import type { CalendarDoc, CalendarEvent, CalendarSource, ScheduledItemMetadata } from './schemas';
|
||||
|
||||
let _syncServer: SyncServer | null = null;
|
||||
|
||||
|
|
@ -313,7 +313,8 @@ routes.post("/api/events", async (c) => {
|
|||
const space = c.req.param("space") || "demo";
|
||||
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 } = body;
|
||||
is_virtual, virtual_url, virtual_platform, r_tool_source, r_tool_entity_id,
|
||||
is_scheduled_item, provenance, item_preview } = body;
|
||||
if (!title?.trim() || !start_time) return c.json({ error: "Title and start_time required" }, 400);
|
||||
|
||||
const docId = calendarDocId(space);
|
||||
|
|
@ -321,6 +322,36 @@ routes.post("/api/events", async (c) => {
|
|||
const eventId = crypto.randomUUID();
|
||||
const now = Date.now();
|
||||
|
||||
// Build metadata for scheduled knowledge items
|
||||
let metadata: ScheduledItemMetadata | null = null;
|
||||
if (is_scheduled_item && provenance) {
|
||||
metadata = {
|
||||
isScheduledItem: true,
|
||||
provenance: {
|
||||
rid: provenance.rid || '',
|
||||
contentHash: provenance.content_hash ?? provenance.contentHash ?? null,
|
||||
sourceType: provenance.source_type ?? provenance.sourceType ?? '',
|
||||
sourceSpace: provenance.source_space ?? provenance.sourceSpace ?? space,
|
||||
lifecycleEvent: provenance.lifecycle_event ?? provenance.lifecycleEvent ?? 'NEW',
|
||||
originalCreatedAt: provenance.original_created_at ?? provenance.originalCreatedAt ?? now,
|
||||
scheduledBy: provenance.scheduled_by ?? provenance.scheduledBy ?? null,
|
||||
scheduledAt: now,
|
||||
},
|
||||
itemPreview: item_preview ? {
|
||||
textPreview: item_preview.text_preview ?? item_preview.textPreview ?? '',
|
||||
mimeType: item_preview.mime_type ?? item_preview.mimeType ?? 'text/plain',
|
||||
thumbnailUrl: item_preview.thumbnail_url ?? item_preview.thumbnailUrl,
|
||||
canvasUrl: item_preview.canvas_url ?? item_preview.canvasUrl,
|
||||
shapeSnapshot: item_preview.shape_snapshot ?? item_preview.shapeSnapshot,
|
||||
} : {
|
||||
textPreview: description || title,
|
||||
mimeType: 'text/plain',
|
||||
},
|
||||
reminderSent: false,
|
||||
reminderSentAt: null,
|
||||
};
|
||||
}
|
||||
|
||||
_syncServer!.changeDoc<CalendarDoc>(docId, `create event ${eventId}`, (d) => {
|
||||
d.events[eventId] = {
|
||||
id: eventId,
|
||||
|
|
@ -346,11 +377,11 @@ routes.post("/api/events", async (c) => {
|
|||
isVirtual: is_virtual || false,
|
||||
virtualUrl: virtual_url || null,
|
||||
virtualPlatform: virtual_platform || null,
|
||||
rToolSource: r_tool_source || null,
|
||||
rToolSource: r_tool_source || 'rSchedule',
|
||||
rToolEntityId: r_tool_entity_id || null,
|
||||
attendees: [],
|
||||
attendeeCount: 0,
|
||||
metadata: null,
|
||||
metadata: metadata,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
|
@ -360,6 +391,41 @@ routes.post("/api/events", async (c) => {
|
|||
return c.json(eventToRow(updated.events[eventId], updated.sources), 201);
|
||||
});
|
||||
|
||||
// GET /api/events/scheduled — query only scheduled knowledge items
|
||||
routes.get("/api/events/scheduled", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
const { date, upcoming, pending_only } = c.req.query();
|
||||
|
||||
const doc = ensureDoc(space);
|
||||
let events = Object.values(doc.events).filter((e) => {
|
||||
const meta = e.metadata as ScheduledItemMetadata | null;
|
||||
return meta?.isScheduledItem === true;
|
||||
});
|
||||
|
||||
if (date) {
|
||||
const dayStart = new Date(date).setHours(0, 0, 0, 0);
|
||||
const dayEnd = dayStart + 86400000;
|
||||
events = events.filter((e) => e.startTime >= dayStart && e.startTime < dayEnd);
|
||||
}
|
||||
|
||||
if (upcoming) {
|
||||
const nowMs = Date.now();
|
||||
const futureMs = nowMs + parseInt(upcoming) * 86400000;
|
||||
events = events.filter((e) => e.startTime >= nowMs && e.startTime <= futureMs);
|
||||
}
|
||||
|
||||
if (pending_only === "true") {
|
||||
events = events.filter((e) => {
|
||||
const meta = e.metadata as ScheduledItemMetadata;
|
||||
return !meta.reminderSent;
|
||||
});
|
||||
}
|
||||
|
||||
events.sort((a, b) => a.startTime - b.startTime);
|
||||
const rows = events.map((e) => eventToRow(e, doc.sources));
|
||||
return c.json({ count: rows.length, results: rows });
|
||||
});
|
||||
|
||||
// GET /api/events/:id
|
||||
routes.get("/api/events/:id", async (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
|
|
@ -399,6 +465,7 @@ routes.patch("/api/events/:id", async (c) => {
|
|||
location_name: 'locationName',
|
||||
is_virtual: 'isVirtual',
|
||||
virtual_url: 'virtualUrl',
|
||||
metadata: 'metadata',
|
||||
};
|
||||
|
||||
const updates: Array<{ field: keyof CalendarEvent; value: any }> = [];
|
||||
|
|
|
|||
|
|
@ -87,6 +87,54 @@ export const calendarSchema: DocSchema<CalendarDoc> = {
|
|||
}),
|
||||
};
|
||||
|
||||
// ── KOI-Inspired Provenance (stored in CalendarEvent.metadata) ──
|
||||
|
||||
/** Lifecycle events following BlockScience KOI's RID pattern */
|
||||
export type ProvenanceLifecycle = 'NEW' | 'UPDATE' | 'FORGET';
|
||||
|
||||
/** KOI-inspired provenance — stored in CalendarEvent.metadata.provenance */
|
||||
export interface KnowledgeProvenance {
|
||||
/** Reference identifier in "type:uuid" format, e.g. "folk-note:abc-123" */
|
||||
rid: string;
|
||||
/** SHA-256 hex digest of content at scheduling time */
|
||||
contentHash: string | null;
|
||||
/** Shape type that originated this item (e.g. "folk-note", "folk-photo") */
|
||||
sourceType: string;
|
||||
/** Space slug where the item lives */
|
||||
sourceSpace: string;
|
||||
/** KOI lifecycle event */
|
||||
lifecycleEvent: ProvenanceLifecycle;
|
||||
/** Original creation timestamp of the source item (epoch ms) */
|
||||
originalCreatedAt: number;
|
||||
/** DID or user identifier who scheduled this */
|
||||
scheduledBy: string | null;
|
||||
/** When the scheduling action occurred (epoch ms) */
|
||||
scheduledAt: number;
|
||||
}
|
||||
|
||||
/** Preview snapshot for email rendering — stored in metadata.itemPreview */
|
||||
export interface ItemPreview {
|
||||
/** First ~200 chars of content */
|
||||
textPreview: string;
|
||||
/** MIME type: text/markdown, image/jpeg, etc. */
|
||||
mimeType: string;
|
||||
/** Thumbnail URL for photos/media */
|
||||
thumbnailUrl?: string;
|
||||
/** Deep link back to canvas shape */
|
||||
canvasUrl?: string;
|
||||
/** Full serialized shape data for reconstruction */
|
||||
shapeSnapshot?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** Typed metadata shape when CalendarEvent represents a scheduled knowledge item */
|
||||
export interface ScheduledItemMetadata {
|
||||
isScheduledItem: true;
|
||||
provenance: KnowledgeProvenance;
|
||||
itemPreview: ItemPreview;
|
||||
reminderSent: boolean;
|
||||
reminderSentAt: number | null;
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
export function calendarDocId(space: string) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue