Spark feed generator template
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(db): update db models

+104 -120
+104 -120
db/models.ts
··· 1 1 import { Document, Model, Schema } from "mongoose"; 2 2 3 + interface RecordRef { 4 + uri: string; 5 + cid: string; 6 + } 7 + 8 + // Plugin for adding author DID population to schemas 9 + function addAuthor(schema: Schema) { 10 + // Only add if schema has authorDid field 11 + if (schema.paths.authorDid) { 12 + schema.virtual("actor", { 13 + ref: "Actor", 14 + localField: "authorDid", 15 + foreignField: "did", 16 + justOne: true, 17 + }); 18 + 19 + // Ensure virtual fields are serialized 20 + schema.set("toJSON", { virtuals: true }); 21 + schema.set("toObject", { virtuals: true }); 22 + } 23 + } 24 + 3 25 // Base interface for documents with authorDid 4 26 interface AuthoredDocument extends Document { 5 27 uri: string; ··· 21 43 $type: string; 22 44 ref: { $link: string }; 23 45 } 24 - 25 - export interface EmbedImage { 46 + export interface ImageMedia extends MediaRef { 26 47 alt: string; 27 - image: MediaRef; 28 48 aspectRatio: { 29 49 width: number; 30 50 height: number; 31 51 }; 32 52 } 33 - 34 - export interface EmbedVideo extends MediaRef { 53 + export interface VideoMedia extends MediaRef { 35 54 alt: string; 36 55 aspectRatio: { 37 56 width: number; 38 57 height: number; 39 58 }; 40 59 } 41 - 42 60 interface Label { 43 61 src: string; 44 62 uri: string; ··· 46 64 val: string; 47 65 neg: boolean; 48 66 } 49 - 50 67 interface Facet { 51 68 index: { 52 69 byteStart: number; ··· 59 76 tag?: string; 60 77 }>; 61 78 } 62 - 63 - export interface PostEmbed { 79 + export interface PostMedia { 64 80 $type: string; 65 - record?: { 66 - uri: string; 67 - cid: string; 68 - }; 69 - alt?: string; 70 - video?: EmbedVideo; 71 - images?: Array<EmbedImage>; 72 - external?: { 73 - uri: string; 74 - title: string; 75 - description: string; 76 - thumb?: MediaRef; 77 - }; 78 - recordWithMedia?: { 79 - record: { 80 - uri: string; 81 - cid: string; 82 - }; 83 - media: { 84 - $type: string; 85 - images?: Array<{ 86 - alt: string; 87 - image: MediaRef; 88 - }>; 89 - }; 90 - }; 81 + video?: VideoMedia; 82 + images?: ImageMedia[]; 91 83 } 92 84 93 - export interface PostDocument extends AuthoredDocument { 94 - text?: string; 85 + export interface Caption { 86 + text: string; 95 87 facets?: Facet[]; 96 - reply: { 97 - root: { 98 - uri: string; 99 - cid: string; 100 - }; 101 - parent: { 102 - uri: string; 103 - cid: string; 104 - }; 105 - } | null; 106 - embed: PostEmbed | null; 107 - sound: { 108 - uri: string; 109 - cid: string; 110 - } | null; 111 - langs?: string[]; 112 - labels: Label[] | null; 113 - tags?: string[]; 114 88 } 115 89 116 - export const postSchema = new Schema<PostDocument>({ 90 + // likes 91 + 92 + export interface LikeDocument extends AuthoredDocument { 93 + subject: string; 94 + subjectCid: string; 95 + via?: string | null; 96 + viaCid?: string | null; 97 + } 98 + export const likeSchema = new Schema<LikeDocument>({ 117 99 ...authoredSchema, 118 - text: { type: String, required: false }, 119 - facets: { type: [Object], required: false, default: [] }, 120 - reply: { 121 - type: { 122 - root: { 123 - uri: { type: String, required: true }, 124 - cid: { type: String, required: true }, 125 - }, 126 - parent: { 127 - uri: { type: String, required: true }, 128 - cid: { type: String, required: true }, 129 - }, 130 - }, 131 - required: false, 132 - default: null, 133 - }, 134 - embed: { type: Object, required: false, default: null }, 135 - sound: { 136 - type: { 137 - uri: { type: String, required: true }, 138 - cid: { type: String, required: true }, 139 - }, 140 - required: false, 141 - default: null, 142 - }, 143 - langs: { type: [String], required: false, default: [] }, 144 - labels: { type: Object, required: false, default: null }, 145 - tags: { type: [String], required: false, default: [] }, 146 - }); 100 + subject: { type: String, required: true, index: true }, 101 + subjectCid: { type: String, required: true }, 102 + via: { type: String, required: false }, 103 + viaCid: { type: String, required: false }, 104 + }) 105 + .index({ authorDid: 1, subject: 1 }, { unique: true }) 106 + .index({ subject: 1, createdAt: -1 }); 147 107 148 - // Compound indexes for more efficient queries 149 - postSchema.index({ authorDid: 1, createdAt: -1 }); 150 - postSchema.index({ tags: 1, createdAt: -1 }); 108 + // follows 151 109 152 110 export interface FollowDocument extends AuthoredDocument { 153 111 subject: string; 154 - type: "sprk" | "bsky"; 155 112 } 156 - 157 113 export const followSchema = new Schema<FollowDocument>({ 158 114 ...authoredSchema, 159 115 subject: { type: String, required: true, index: true }, 160 - type: { 161 - type: String, 162 - required: true, 163 - enum: ["sprk", "bsky"], 164 - index: true, 165 - default: "sprk", 166 - }, 167 - }); 116 + }) 117 + .index({ authorDid: 1, subject: 1 }, { unique: true }) 118 + .index({ subject: 1, createdAt: -1 }); 168 119 169 - export interface LikeDocument extends AuthoredDocument { 120 + // reposts 121 + 122 + export interface RepostDocument extends AuthoredDocument { 170 123 subject: string; 171 124 subjectCid: string; 172 125 via?: string | null; 173 126 viaCid?: string | null; 174 127 } 175 - 176 - export const likeSchema = new Schema<LikeDocument>({ 128 + export const repostSchema = new Schema<RepostDocument>({ 177 129 ...authoredSchema, 178 - subject: { type: String, required: true, index: true }, 130 + subject: { type: String, required: true }, 179 131 subjectCid: { type: String, required: true }, 180 132 via: { type: String, required: false }, 181 133 viaCid: { type: String, required: false }, 182 - }); 134 + }) 135 + .index({ subject: 1, createdAt: -1 }) 136 + .index({ authorDid: 1, createdAt: -1 }); 183 137 184 - export interface RepostDocument extends AuthoredDocument { 185 - subject: { 186 - uri: string; 187 - cid: string; 188 - }; 189 - via?: string | null; 190 - viaCid?: string | null; 191 - } 138 + // posts 192 139 193 - export const repostSchema = new Schema<RepostDocument>({ 140 + export interface PostDocument extends AuthoredDocument { 141 + caption?: Caption; 142 + media?: PostMedia; 143 + sound?: RecordRef; 144 + langs?: string[]; 145 + labels?: Label[]; 146 + tags?: string[]; 147 + likeCount: number; 148 + replyCount: number; 149 + repostCount: number; 150 + } 151 + export const postSchema = new Schema<PostDocument>({ 194 152 ...authoredSchema, 195 - subject: { 196 - uri: { type: String, required: true }, 197 - cid: { type: String, required: true }, 153 + caption: { 154 + type: { 155 + text: { type: String, required: true }, 156 + facets: { type: [Object], required: false, default: [] }, 157 + }, 158 + required: false, 198 159 }, 199 - via: { type: String, required: false }, 200 - viaCid: { type: String, required: false }, 201 - }); 160 + media: { type: Object, required: false }, 161 + sound: { 162 + type: { 163 + uri: { type: String, required: true }, 164 + cid: { type: String, required: true }, 165 + }, 166 + required: false, 167 + }, 168 + langs: { type: [String], required: false, default: [] }, 169 + labels: { type: [Object], required: false, default: [] }, 170 + tags: { type: [String], required: false, default: [] }, 171 + likeCount: { type: Number, required: true, default: 0 }, 172 + replyCount: { type: Number, required: true, default: 0 }, 173 + repostCount: { type: Number, required: true, default: 0 }, 174 + }) 175 + .index({ authorDid: 1, createdAt: -1 }) 176 + .index({ tags: 1, createdAt: -1 }); 177 + 178 + // cursor state 202 179 203 180 export interface CursorStateDocument extends Document { 204 181 identifier: string; // To ensure a single document, e.g., 'last_processed_cursor' 205 182 cursorValue: number; 206 183 updatedAt: Date; 207 184 } 208 - 209 185 export const cursorStateSchema = new Schema<CursorStateDocument>({ 210 186 identifier: { type: String, required: true, unique: true, index: true }, 211 187 cursorValue: { type: Number, required: true }, 212 188 updatedAt: { type: Date, default: Date.now }, 213 189 }); 214 190 191 + // Apply plugin to schemas that extend AuthoredDocument 192 + ([ 193 + likeSchema, 194 + postSchema, 195 + repostSchema, 196 + followSchema, 197 + ] as Schema[]).forEach((s) => s.plugin(addAuthor)); 198 + 215 199 export interface DatabaseModels { 200 + Like: Model<LikeDocument>; 216 201 Post: Model<PostDocument>; 217 202 Follow: Model<FollowDocument>; 218 - Like: Model<LikeDocument>; 219 203 Repost: Model<RepostDocument>; 220 204 CursorState: Model<CursorStateDocument>; 221 205 }