[READ ONLY MIRROR] Spark Social AppView Server github.com/sprksocial/server
atproto deno hono lexicon
5
fork

Configure Feed

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

at eb947da8a3a8a7d485ff132b3ff0db4a8baaac19 190 lines 4.7 kB view raw
1import { CID } from "multiformats/cid"; 2import { AtUri, normalizeDatetimeAlways } from "@atp/syntax"; 3import * as lex from "../../../lex/lexicons.ts"; 4import * as Like from "../../../lex/types/so/sprk/feed/like.ts"; 5import { BackgroundQueue } from "../../background.ts"; 6import { Database } from "../../db/index.ts"; 7import { LikeDocument } from "../../db/models.ts"; 8import { RecordProcessor } from "../processor.ts"; 9 10const lexId = lex.ids.SoSprkFeedLike; 11type IndexedLike = LikeDocument; 12 13const insertFn = async ( 14 db: Database, 15 uri: AtUri, 16 cid: CID, 17 obj: Like.Record, 18 timestamp: string, 19): Promise<IndexedLike | null> => { 20 // Handle via property safely with type assertion 21 const viaObj = obj.via as { uri: string; cid: string } | undefined; 22 const via = viaObj?.uri || null; 23 const viaCid = viaObj?.cid || null; 24 25 const like = { 26 uri: uri.toString(), 27 cid: cid.toString(), 28 authorDid: uri.host, 29 subject: obj.subject.uri, 30 subjectCid: obj.subject.cid, 31 via, 32 viaCid, 33 createdAt: normalizeDatetimeAlways(obj.createdAt), 34 indexedAt: timestamp, 35 }; 36 37 const insertedLike = await db.models.Like.findOneAndUpdate( 38 { uri: like.uri }, 39 { $set: like }, 40 { upsert: true, new: true }, 41 ); 42 return insertedLike; 43}; 44 45const findDuplicate = async ( 46 db: Database, 47 uri: AtUri, 48 obj: Like.Record, 49): Promise<AtUri | null> => { 50 const found = await db.models.Like.findOne({ 51 authorDid: uri.host, 52 subject: obj.subject.uri, 53 }).lean(); 54 return found ? new AtUri(found.uri) : null; 55}; 56 57const notifsForInsert = (obj: IndexedLike) => { 58 const subjectUri = new AtUri(obj.subject); 59 // prevent self-notifications 60 const isLikeFromSubjectUser = subjectUri.host === obj.authorDid; 61 if (isLikeFromSubjectUser) { 62 return []; 63 } 64 65 const notifs: Array<{ 66 did: string; 67 reason: string; 68 author: string; 69 recordUri: string; 70 recordCid: string; 71 sortAt: string; 72 reasonSubject?: string; 73 }> = [ 74 // Notification to the author of the liked record. 75 { 76 did: subjectUri.host, 77 author: obj.authorDid, 78 recordUri: obj.uri, 79 recordCid: obj.cid, 80 reason: "like" as const, 81 reasonSubject: subjectUri.toString(), 82 sortAt: obj.createdAt, 83 }, 84 ]; 85 86 if (obj.via) { 87 const viaUri = new AtUri(obj.via); 88 const isLikeFromViaSubjectUser = viaUri.host === obj.authorDid; 89 // prevent self-notifications 90 if (!isLikeFromViaSubjectUser) { 91 notifs.push( 92 // Notification to the reposter via whose repost the like was made. 93 { 94 did: viaUri.host, 95 author: obj.authorDid, 96 recordUri: obj.uri, 97 recordCid: obj.cid, 98 reason: "like-via-repost" as const, 99 reasonSubject: viaUri.toString(), 100 sortAt: obj.createdAt, 101 }, 102 ); 103 } 104 } 105 106 return notifs; 107}; 108 109const deleteFn = async ( 110 db: Database, 111 uri: AtUri, 112): Promise<IndexedLike | null> => { 113 const deleted = await db.models.Like.findOneAndDelete({ 114 uri: uri.toString(), 115 }); 116 return deleted; 117}; 118 119const notifsForDelete = ( 120 deleted: IndexedLike, 121 replacedBy: IndexedLike | null, 122) => { 123 const toDelete = replacedBy ? [] : [deleted.uri]; 124 return { notifs: [], toDelete }; 125}; 126 127const updateAggregates = async (db: Database, like: IndexedLike) => { 128 const likeCount = await db.models.Like.countDocuments({ 129 subject: like.subject, 130 }); 131 132 const subjectUri = new AtUri(like.subject); 133 134 if (subjectUri.collection === "so.sprk.feed.generator") { 135 const existingGenerator = await db.models.Generator.findOne({ 136 uri: like.subject, 137 }); 138 139 if (existingGenerator) { 140 await db.models.Generator.findOneAndUpdate( 141 { uri: like.subject }, 142 { $set: { likeCount } }, 143 { new: true }, 144 ); 145 } 146 } else { 147 const existingPost = await db.models.Post.findOne({ 148 uri: like.subject, 149 }); 150 151 if (existingPost) { 152 await db.models.Post.findOneAndUpdate( 153 { uri: like.subject }, 154 { $set: { likeCount } }, 155 { new: true }, 156 ); 157 } 158 159 const existingReply = await db.models.Reply.findOne({ 160 uri: like.subject, 161 }); 162 163 if (existingReply) { 164 await db.models.Reply.findOneAndUpdate( 165 { uri: like.subject }, 166 { $set: { likeCount } }, 167 { new: true }, 168 ); 169 } 170 } 171}; 172 173export type PluginType = RecordProcessor<Like.Record, IndexedLike>; 174 175export const makePlugin = ( 176 db: Database, 177 background: BackgroundQueue, 178): PluginType => { 179 return new RecordProcessor(db, background, { 180 lexId, 181 insertFn, 182 findDuplicate, 183 deleteFn, 184 notifsForInsert, 185 notifsForDelete, 186 updateAggregates, 187 }); 188}; 189 190export default makePlugin;