import { Document, Model, Schema } from "mongoose"; interface RecordRef { uri: string; cid: string; } // Plugin for adding author DID population to schemas function addAuthor(schema: Schema) { // Only add if schema has authorDid field if (schema.paths.authorDid) { schema.virtual("actor", { ref: "Actor", localField: "authorDid", foreignField: "did", justOne: true, }); // Ensure virtual fields are serialized schema.set("toJSON", { virtuals: true }); schema.set("toObject", { virtuals: true }); } } // Base interface for documents with authorDid interface AuthoredDocument extends Document { uri: string; cid: string; createdAt: string; indexedAt: string; authorDid: string; } export const authoredSchema = { uri: { type: String, required: true, unique: true, index: true }, authorDid: { type: String, required: true, index: true }, cid: { type: String, required: true }, createdAt: { type: String, required: true }, indexedAt: { type: String, required: true }, }; export interface MediaRef { $type: string; ref: { $link: string }; } export interface ImageMedia extends MediaRef { alt: string; aspectRatio: { width: number; height: number; }; } export interface VideoMedia extends MediaRef { alt: string; aspectRatio: { width: number; height: number; }; } interface Label { src: string; uri: string; cid: string; val: string; neg: boolean; } interface Facet { index: { byteStart: number; byteEnd: number; }; features: Array<{ $type: string; uri?: string; did?: string; tag?: string; }>; } export interface PostMedia { $type: string; video?: VideoMedia; images?: ImageMedia[]; } export interface Caption { text: string; facets?: Facet[]; } // likes export interface LikeDocument extends AuthoredDocument { subject: string; subjectCid: string; via?: string | null; viaCid?: string | null; } export const likeSchema = new Schema({ ...authoredSchema, subject: { type: String, required: true, index: true }, subjectCid: { type: String, required: true }, via: { type: String, required: false }, viaCid: { type: String, required: false }, }) .index({ authorDid: 1, subject: 1 }, { unique: true }) .index({ subject: 1, createdAt: -1 }); // follows export interface FollowDocument extends AuthoredDocument { subject: string; } export const followSchema = new Schema({ ...authoredSchema, subject: { type: String, required: true, index: true }, }) .index({ authorDid: 1, subject: 1 }, { unique: true }) .index({ subject: 1, createdAt: -1 }); // reposts export interface RepostDocument extends AuthoredDocument { subject: string; subjectCid: string; via?: string | null; viaCid?: string | null; } export const repostSchema = new Schema({ ...authoredSchema, subject: { type: String, required: true }, subjectCid: { type: String, required: true }, via: { type: String, required: false }, viaCid: { type: String, required: false }, }) .index({ subject: 1, createdAt: -1 }) .index({ authorDid: 1, createdAt: -1 }); // posts export interface PostDocument extends AuthoredDocument { caption?: Caption; media?: PostMedia; sound?: RecordRef; langs?: string[]; labels?: Label[]; tags?: string[]; likeCount: number; replyCount: number; repostCount: number; } export const postSchema = new Schema({ ...authoredSchema, caption: { type: { text: { type: String, required: true }, facets: { type: [Object], required: false, default: [] }, }, required: false, }, media: { type: Object, required: false }, sound: { type: { uri: { type: String, required: true }, cid: { type: String, required: true }, }, required: false, }, langs: { type: [String], required: false, default: [] }, labels: { type: [Object], required: false, default: [] }, tags: { type: [String], required: false, default: [] }, likeCount: { type: Number, required: true, default: 0 }, replyCount: { type: Number, required: true, default: 0 }, repostCount: { type: Number, required: true, default: 0 }, }) .index({ authorDid: 1, createdAt: -1 }) .index({ tags: 1, createdAt: -1 }); // cursor state export interface CursorStateDocument extends Document { identifier: string; // To ensure a single document, e.g., 'last_processed_cursor' cursorValue: number; updatedAt: Date; } export const cursorStateSchema = new Schema({ identifier: { type: String, required: true, unique: true, index: true }, cursorValue: { type: Number, required: true }, updatedAt: { type: Date, default: Date.now }, }); // Apply plugin to schemas that extend AuthoredDocument ([ likeSchema, postSchema, repostSchema, followSchema, ] as Schema[]).forEach((s) => s.plugin(addAuthor)); export interface DatabaseModels { Like: Model; Post: Model; Follow: Model; Repost: Model; CursorState: Model; }