unoffical wafrn mirror
wafrn.net
atproto
social-network
activitypub
1import {
2 Model,
3 Table,
4 Column,
5 DataType,
6 ForeignKey,
7 HasMany,
8 HasOne,
9 BelongsToMany,
10 BelongsTo,
11} from "sequelize-typescript";
12import { Notification } from "./notification.js";
13import { Ask } from "./ask.js";
14import { QuestionPoll } from "./questionPoll.js";
15import { EmojiReaction } from "./emojiReaction.js";
16import { Emoji } from "./emoji.js";
17import { PostEmojiRelations } from "./postEmojiRelations.js";
18import { Quotes } from "./quotes.js";
19import { PostReport } from "./postReport.js";
20import { SilencedPost } from "./silencedPost.js";
21import { PostTag } from "./postTag.js";
22import { User } from "./user.js";
23import { Media } from "./media.js";
24import { PostMentionsUserRelation } from "./postMentionsUserRelation.js";
25import { UserLikesPostRelations } from "./userLikesPostRelations.js";
26import { UserBookmarkedPosts } from "./userBookmarkedPosts.js";
27import { PostHostView } from "./postHostView.js";
28import { RemoteUserPostView } from "./remoteUserPostView.js";
29import { FederatedHost } from "./federatedHost.js";
30import { PostAncestor } from "./postAncestor.js";
31import {
32 BelongsToGetAssociationMixin,
33 BelongsToManyGetAssociationsMixin,
34 BelongsToManySetAssociationsMixin,
35 BelongsToSetAssociationMixin,
36 HasManyGetAssociationsMixin,
37 HasManyRemoveAssociationMixin,
38 HasManyRemoveAssociationsMixin,
39 HasManySetAssociationsMixin,
40 HasOneGetAssociationMixin,
41} from "sequelize";
42import { completeEnvironment } from "../utils/backendOptions.js";
43
44export const Privacy = {
45 Public: 0,
46 FollowersOnly: 1,
47 LocalOnly: 2,
48 Unlisted: 3,
49 DirectMessage: 10,
50 LinkOnly: 20,
51} as const;
52
53export const InteractionControl = {
54 Anyone: 0,
55 Followers: 1,
56 Following: 2,
57 FollowersAndFollowing: 3,
58 FollowersAndMentioned: 4,
59 FollowingAndMentioned: 5,
60 FollowersFollowersAndMentioned: 6,
61 MentionedUsersOnly: 7,
62 NoOne: 8,
63 SameAsOp: 100, // this one is bsky exclusive and its gona be FUN (a headache). This only applies to REPLIES. Nothing else.
64};
65
66export type PrivacyType = typeof Privacy[keyof typeof Privacy];
67
68export type InteractionControlType =
69 typeof InteractionControl[keyof typeof InteractionControl];
70
71export interface PostAttributes {
72 id?: string;
73 createdAt?: Date;
74 updatedAt?: Date;
75 content_warning?: string | null;
76 content?: string;
77 markdownContent?: string;
78 title?: string;
79 slug?: string;
80 remotePostId?: string | null;
81 bskyUri?: string | null;
82 bskyCid?: string | null;
83 privacy?: PrivacyType;
84 featured?: Date | null;
85 isReblog?: boolean;
86 isDeleted?: boolean;
87 userId?: string;
88 hierarchyLevel?: number;
89 parentId?: string;
90 replyControl?: InteractionControlType;
91 likeControl?: InteractionControlType;
92 reblogControl?: InteractionControlType;
93 quoteControl?: InteractionControlType;
94 displayUrl: String | null;
95}
96
97@Table({
98 tableName: "posts",
99 modelName: "posts",
100 timestamps: true,
101})
102export class Post
103 extends Model<PostAttributes, PostAttributes>
104 implements PostAttributes
105{
106 @Column({
107 primaryKey: true,
108 type: DataType.UUID,
109 defaultValue: DataType.UUIDV4,
110 })
111 declare id: string;
112
113 @Column({
114 allowNull: true,
115 type: DataType.STRING,
116 })
117 declare content_warning: string;
118
119 @Column({
120 allowNull: true,
121 type: DataType.STRING,
122 })
123 declare content: string;
124
125 @Column({
126 allowNull: true,
127 type: DataType.STRING,
128 })
129 declare markdownContent: string;
130
131 @Column({
132 allowNull: true,
133 type: DataType.STRING(256),
134 })
135 declare title: string;
136
137 @Column({
138 allowNull: true,
139 type: DataType.STRING(256),
140 })
141 declare slug: string;
142
143 @Column({
144 allowNull: true,
145 type: DataType.STRING(768),
146 })
147 declare remotePostId: string | null;
148
149 @Column({
150 allowNull: true,
151 type: DataType.STRING(768),
152 })
153 declare bskyUri: string | null;
154
155 @Column({
156 allowNull: true,
157 type: DataType.STRING(768),
158 })
159 declare bskyCid: string | null;
160
161 @Column({
162 allowNull: true,
163 type: DataType.INTEGER,
164 })
165 declare privacy: PrivacyType;
166
167 @Column({
168 allowNull: true,
169 type: DataType.DATE,
170 defaultValue: null,
171 })
172 declare featured: Date | null;
173
174 @Column({
175 allowNull: true,
176 type: DataType.BOOLEAN,
177 defaultValue: false,
178 })
179 declare isReblog: boolean;
180
181 @Column({
182 allowNull: true,
183 type: DataType.BOOLEAN,
184 defaultValue: false,
185 })
186 declare isDeleted: boolean;
187
188 @ForeignKey(() => User)
189 @Column({
190 allowNull: true,
191 type: DataType.UUID,
192 })
193 declare userId: string;
194
195 @Column({
196 allowNull: true,
197 type: DataType.INTEGER,
198 })
199 declare hierarchyLevel: number;
200
201 @Column({
202 allowNull: true,
203 type: DataType.STRING,
204 })
205 declare displayUrl: string;
206
207 @ForeignKey(() => Post)
208 @Column({
209 allowNull: true,
210 type: DataType.UUID,
211 })
212 declare parentId: string;
213
214 @Column({
215 allowNull: true,
216 type: DataType.INTEGER,
217 defaultValue: 0,
218 })
219 declare replyControl: InteractionControlType;
220
221 @Column({
222 allowNull: true,
223 type: DataType.INTEGER,
224 defaultValue: 0,
225 })
226 declare likeControl: InteractionControlType;
227
228 @Column({
229 allowNull: true,
230 type: DataType.INTEGER,
231 defaultValue: 0,
232 })
233 declare reblogControl: InteractionControlType;
234
235 @Column({
236 allowNull: true,
237 type: DataType.INTEGER,
238 defaultValue: 0,
239 })
240 declare quoteControl: InteractionControlType;
241
242 @BelongsTo(() => Post, "parentId")
243 declare parent: Post;
244 declare getParent: BelongsToGetAssociationMixin<Post>;
245 declare setParent: BelongsToSetAssociationMixin<Post, string>;
246
247 @HasMany(() => Post, "parentId")
248 declare children: Post[];
249
250 @BelongsToMany(() => Post, () => PostAncestor, "postsId", "ancestorId")
251 declare ancestors: Post[];
252 declare getAncestors: BelongsToManyGetAssociationsMixin<Post>;
253
254 @BelongsToMany(() => Post, () => PostAncestor, "ancestorId", "postsId")
255 declare descendents: Post[];
256 declare getDescendents: BelongsToManyGetAssociationsMixin<Post>;
257
258 @HasMany(() => Notification, {
259 sourceKey: "id",
260 })
261 declare notifications: Notification[];
262
263 @HasOne(() => Ask, {
264 sourceKey: "id",
265 })
266 declare ask: Ask;
267 declare getAsk: HasOneGetAssociationMixin<Ask>;
268
269 @HasOne(() => QuestionPoll, {
270 sourceKey: "id",
271 })
272 declare questionPoll: QuestionPoll;
273
274 @HasMany(() => EmojiReaction, {
275 sourceKey: "id",
276 })
277 declare emojiReacions: EmojiReaction[];
278
279 @BelongsToMany(() => Emoji, () => PostEmojiRelations)
280 declare emojis: Emoji[];
281 declare getEmojis: BelongsToManyGetAssociationsMixin<Emoji>;
282
283 @HasMany(() => Quotes, {
284 foreignKey: "quoterPostId",
285 })
286 declare quoterQuotes: Quotes[];
287
288 @HasMany(() => Quotes, {
289 foreignKey: "quotedPostId",
290 })
291 declare quotedQuotes: Quotes[];
292
293 @BelongsToMany(() => Post, () => Quotes, "quoterPostId", "quotedPostId")
294 declare quoted: Post[];
295 declare setQuoted: BelongsToManySetAssociationsMixin<Post, string>;
296
297 @BelongsToMany(() => Post, () => Quotes, "quotedPostId", "quoterPostId")
298 declare quoter: Post[];
299
300 @HasMany(() => PostReport, {
301 sourceKey: "id",
302 })
303 declare postReports: PostReport[];
304
305 @HasMany(() => SilencedPost, {
306 sourceKey: "id",
307 })
308 declare silencedPosts: SilencedPost[];
309
310 @HasMany(() => PostTag, {
311 sourceKey: "id",
312 })
313 declare postTags: PostTag[];
314 declare getPostTags: HasManyGetAssociationsMixin<PostTag>;
315
316 @BelongsTo(() => User)
317 declare user: User;
318 declare getUser: BelongsToGetAssociationMixin<User>;
319
320 @HasMany(() => Media, {
321 sourceKey: "id",
322 })
323 declare medias: Media[];
324 declare setMedias: HasManySetAssociationsMixin<Media, number>;
325 declare getMedias: HasManyGetAssociationsMixin<Media>;
326 declare removeMedias: HasManyRemoveAssociationsMixin<Media, number>;
327
328 @HasMany(() => PostMentionsUserRelation, {
329 sourceKey: "id",
330 })
331 declare pMURs: PostMentionsUserRelation[];
332
333 @HasMany(() => UserLikesPostRelations, {
334 sourceKey: "id",
335 })
336 declare userLikesPostRelations: UserLikesPostRelations[];
337
338 @BelongsToMany(() => User, () => PostMentionsUserRelation)
339 declare mentionPost: User[];
340 declare getMentionPost: BelongsToManyGetAssociationsMixin<User>;
341
342 @HasMany(() => UserBookmarkedPosts, {
343 sourceKey: "id",
344 })
345 declare userBookmarkedPosts: UserBookmarkedPosts[];
346
347 @HasMany(() => PostHostView, {
348 sourceKey: "id",
349 })
350 declare postHostViewList: PostHostView[];
351
352 @BelongsToMany(() => FederatedHost, () => PostHostView)
353 declare hostView: FederatedHost[];
354
355 @HasMany(() => RemoteUserPostView, {
356 sourceKey: "id",
357 })
358 declare remoteUserPostViewList: RemoteUserPostView[];
359
360 @BelongsToMany(() => User, () => RemoteUserPostView)
361 declare view: User[];
362
363 static get hierarchy() {
364 return {
365 as: "parent",
366 childrenAs: "children",
367 ancestorsAs: "ancestors",
368 descendentsAs: "descendents",
369 primaryKey: "id",
370 foreignKey: "parentId",
371 levelFieldName: "hierarchyLevel",
372 through: PostAncestor,
373 throughKey: "postsId",
374 throughForeignKey: "ancestorId",
375 throughTable: "postancestors",
376 };
377 }
378
379 get hierarchy() {
380 return Post.hierarchy;
381 }
382
383 get fullUrl() {
384 return (
385 this.remotePostId ||
386 `${completeEnvironment.frontendUrl}/fediverse/post/${this.id}`
387 );
388 }
389
390 // only use if the user is already loaded into the object, otherwise use `await isRemoteBlueskyPostAsync()`
391 get isRemoteBlueskyPost() {
392 return this.bskyUri && this.user.isRemoteUser && !this.remotePostId;
393 }
394
395 async isRemoteBlueskyPostAsync() {
396 return this.bskyUri && (await this.getUser()).isRemoteUser;
397 }
398
399 get postShouldGoFedi() {
400 return this.remotePostId || !this.user.isRemoteUser;
401 }
402
403 async fullUrlIncludingBsky() {
404 return (
405 this.remotePostId ||
406 ((await this.isRemoteBlueskyPostAsync())
407 ? this.bskyUri
408 : `${completeEnvironment.frontendUrl}/fediverse/post/${this.id}`)
409 );
410 }
411}