171 lines
3.5 KiB
TypeScript
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;
|
|
}
|