rspace-online/modules/rsocials/schemas.ts

171 lines
3.5 KiB
TypeScript

/**
* rSocials Automerge document schemas.
*
* Granularity: one Automerge document per space.
* DocId format: {space}:socials:data
*
* Images stay on filesystem, referenced by URL strings in the doc.
*/
import type { DocSchema } from '../../shared/local-first/document';
// ── Thread types ──
export interface ThreadData {
id: string;
name: string;
handle: string;
title: string;
tweets: string[];
imageUrl?: string | null;
tweetImages?: Record<string, string> | null;
createdAt: number;
updatedAt: number;
}
// ── Campaign types ──
export interface CampaignPost {
id: string;
platform: string;
postType: string;
stepNumber: number;
content: string;
scheduledAt: string;
status: string;
hashtags: string[];
phase: number;
phaseLabel: string;
}
export interface Campaign {
id: string;
title: string;
description: string;
duration: string;
platforms: string[];
phases: { name: string; label: string; days: string }[];
posts: CampaignPost[];
createdAt: number;
updatedAt: number;
}
// ── Campaign planner (flow canvas) types ──
export type CampaignNodeType = 'post' | 'thread' | 'platform' | 'audience' | 'phase';
export interface PostNodeData {
label: string;
platform: string;
postType: string;
content: string;
scheduledAt: string;
status: 'draft' | 'scheduled' | 'published';
hashtags: string[];
}
export interface ThreadNodeData {
label: string;
threadId: string;
tweetCount: number;
status: 'draft' | 'ready' | 'published';
preview: string;
}
export interface PlatformNodeData {
label: string;
platform: string;
handle: string;
}
export interface AudienceNodeData {
label: string;
description: string;
sizeEstimate: string;
}
export interface PhaseNodeData {
label: string;
dateRange: string;
color: string;
progress?: number;
childNodeIds: string[];
size: { w: number; h: number };
}
export interface CampaignPlannerNode {
id: string;
type: CampaignNodeType;
position: { x: number; y: number };
data: PostNodeData | ThreadNodeData | PlatformNodeData | AudienceNodeData | PhaseNodeData;
}
export type CampaignEdgeType = 'publish' | 'sequence' | 'target';
export interface CampaignEdge {
id: string;
from: string;
to: string;
type: CampaignEdgeType;
waypoint?: { x: number; y: number };
}
export interface CampaignFlow {
id: string;
name: string;
nodes: CampaignPlannerNode[];
edges: CampaignEdge[];
createdAt: number;
updatedAt: number;
createdBy: string | null;
}
// ── Document root ──
export interface SocialsDoc {
meta: {
module: string;
collection: string;
version: number;
spaceSlug: string;
createdAt: number;
};
threads: Record<string, ThreadData>;
campaigns: Record<string, Campaign>;
campaignFlows: Record<string, CampaignFlow>;
activeFlowId: string;
}
// ── Schema registration ──
export const socialsSchema: DocSchema<SocialsDoc> = {
module: 'socials',
collection: 'data',
version: 2,
init: (): SocialsDoc => ({
meta: {
module: 'socials',
collection: 'data',
version: 2,
spaceSlug: '',
createdAt: Date.now(),
},
threads: {},
campaigns: {},
campaignFlows: {},
activeFlowId: '',
}),
migrate: (doc: SocialsDoc, _fromVersion: number): SocialsDoc => {
if (!doc.campaignFlows) (doc as any).campaignFlows = {};
if (!doc.activeFlowId) (doc as any).activeFlowId = '';
if (doc.meta) doc.meta.version = 2;
return doc;
},
};
// ── Helpers ──
export function socialsDocId(space: string) {
return `${space}:socials:data` as const;
}