unoffical wafrn mirror
wafrn.net
atproto
social-network
activitypub
1import {
2 Model,
3 Table,
4 Column,
5 DataType,
6 ForeignKey,
7 HasMany,
8 BelongsToMany,
9 BelongsTo,
10} from "sequelize-typescript";
11import { MfaDetails } from "./mfaDetails.js";
12import { Notification } from "./notification.js";
13import { Ask } from "./ask.js";
14import { QuestionPollAnswer } from "./questionPollAnswer.js";
15import { EmojiReaction } from "./emojiReaction.js";
16import { UserOptions } from "./userOptions.js";
17import { PushNotificationToken } from "./pushNotificationToken.js";
18import { Emoji } from "./emoji.js";
19import { UserEmojiRelation } from "./userEmojiRelation.js";
20import { Follows } from "./follows.js";
21import { Blocks } from "./blocks.js";
22import { Mutes } from "./mutes.js";
23import { ServerBlock } from "./serverBlock.js";
24import { PostReport } from "./postReport.js";
25import { SilencedPost } from "./silencedPost.js";
26import { FederatedHost } from "./federatedHost.js";
27import { Post } from "./post.js";
28import { Media } from "./media.js";
29import { PostMentionsUserRelation } from "./postMentionsUserRelation.js";
30import { UserBitesPostRelation } from "./userBitesPostRelation.js";
31import { UserLikesPostRelations } from "./userLikesPostRelations.js";
32import { UserBookmarkedPosts } from "./userBookmarkedPosts.js";
33import { RemoteUserPostView } from "./remoteUserPostView.js";
34import {
35 BelongsToGetAssociationMixin,
36 BelongsToManyAddAssociationMixin,
37 BelongsToManyGetAssociationsMixin,
38 BelongsToManyRemoveAssociationMixin,
39 BelongsToManyRemoveAssociationsMixin,
40 BelongsToManySetAssociationsMixin,
41 HasManyGetAssociationsMixin,
42 HasManyRemoveAssociationMixin,
43} from "sequelize";
44import { Col } from "sequelize/lib/utils";
45import { UserFollowHashtags } from "./userFollowHashtag.js";
46import { completeEnvironment } from "../utils/backendOptions.js";
47import { Bites } from "./bites.js";
48
49export interface UserAttributes {
50 id?: string;
51 createdAt?: Date;
52 updatedAt?: Date;
53 email?: string | null;
54 description?: string;
55 descriptionMarkdown?: string;
56 name?: string;
57 url: string;
58 NSFW?: boolean;
59 avatar?: string;
60 password?: string;
61 birthDate?: Date;
62 activated?: boolean | null;
63 requestedPasswordReset?: Date | null;
64 activationCode?: string;
65 registerIp?: string;
66 lastLoginIp?: string;
67 lastTimeNotificationsCheck?: Date;
68 privateKey?: string | null;
69 publicKey?: string;
70 federatedHostId?: string | null;
71 remoteInbox?: string;
72 remoteId?: string;
73 remoteMentionUrl?: string;
74 isBot?: boolean;
75 banned?: boolean | null;
76 role?: number;
77 manuallyAcceptsFollows?: boolean;
78 headerImage?: string;
79 followersCollectionUrl?: string;
80 followingCollectionUrl?: string;
81 followerCount?: number;
82 followingCount?: number;
83 disableEmailNotifications?: boolean;
84 enableBsky?: boolean;
85 bskyAuthData?: string | null;
86 bskyAppPassword?: string | null;
87 bskyDid?: string | null;
88 lastActiveAt?: Date;
89 hideFollows?: Boolean;
90 hideProfileNotLoggedIn?: Boolean;
91 emailVerified: Boolean | null;
92 selfDeleted: Boolean | null;
93 userMigratedTo: String | null;
94 bskyInviteCode: String | null;
95 displayUrl: String | null;
96}
97
98@Table({
99 tableName: "users",
100 modelName: "users",
101 timestamps: true,
102 scopes: {
103 full: {
104 //attributes: {}
105 },
106 },
107 defaultScope: {
108 attributes: {
109 exclude: [
110 "password",
111 "email",
112 "privateKey",
113 "lastLoginIp",
114 "registerIp",
115 "bskyAuthData",
116 "bskyAppPassword",
117 "birthDate",
118 "bskyInviteCode",
119 ],
120 },
121 },
122})
123export class User
124 extends Model<UserAttributes, UserAttributes>
125 implements UserAttributes
126{
127 @Column({
128 primaryKey: true,
129 type: DataType.UUID,
130 defaultValue: DataType.UUIDV4,
131 })
132 declare id: string;
133
134 @Column({
135 allowNull: true,
136 type: DataType.STRING(768),
137 })
138 declare email: string | null;
139
140 @Column({
141 allowNull: true,
142 type: DataType.STRING,
143 })
144 declare description: string;
145
146 @Column({
147 allowNull: true,
148 type: DataType.STRING,
149 })
150 declare descriptionMarkdown: string;
151
152 @Column({
153 allowNull: true,
154 type: DataType.STRING,
155 })
156 declare name: string;
157
158 @Column({
159 type: DataType.STRING(768),
160 })
161 declare url: string;
162
163 @Column({
164 allowNull: true,
165 type: DataType.BOOLEAN,
166 defaultValue: false,
167 })
168 declare NSFW: boolean;
169
170 @Column({
171 allowNull: true,
172 type: DataType.STRING,
173 })
174 declare avatar: string;
175
176 @Column({
177 allowNull: true,
178 type: DataType.STRING,
179 })
180 declare password: string;
181
182 @Column({
183 allowNull: true,
184 type: DataType.DATE,
185 })
186 declare birthDate: Date;
187
188 @Column({
189 allowNull: true,
190 type: DataType.BOOLEAN,
191 })
192 declare activated: boolean | null;
193
194 @Column({
195 allowNull: true,
196 type: DataType.BOOLEAN,
197 })
198 declare emailVerified: boolean | null;
199
200 @Column({
201 allowNull: true,
202 type: DataType.DATE,
203 })
204 declare requestedPasswordReset: Date | null;
205
206 @Column({
207 allowNull: true,
208 type: DataType.STRING(255),
209 })
210 declare activationCode: string;
211
212 @Column({
213 allowNull: true,
214 type: DataType.STRING(255),
215 })
216 declare registerIp: string;
217
218 @Column({
219 allowNull: true,
220 type: DataType.STRING(255),
221 })
222 declare lastLoginIp: string;
223
224 @Column({
225 type: DataType.DATE,
226 defaultValue: new Date(0),
227 })
228 declare lastTimeNotificationsCheck: Date;
229
230 @Column({
231 allowNull: true,
232 type: DataType.STRING,
233 })
234 declare privateKey: string | null;
235
236 @Column({
237 allowNull: true,
238 type: DataType.STRING,
239 })
240 declare publicKey: string;
241
242 @ForeignKey(() => FederatedHost)
243 @Column({
244 allowNull: true,
245 type: DataType.UUID,
246 })
247 declare federatedHostId: string | null;
248
249 @Column({
250 allowNull: true,
251 type: DataType.STRING,
252 })
253 declare remoteInbox: string;
254
255 @Column({
256 allowNull: true,
257 type: DataType.STRING(768),
258 })
259 declare remoteId: string;
260
261 @Column({
262 allowNull: true,
263 type: DataType.STRING,
264 })
265 declare remoteMentionUrl: string;
266
267 @Column({
268 allowNull: true,
269 type: DataType.BOOLEAN,
270 defaultValue: false,
271 })
272 declare isBot: boolean;
273
274 @Column({
275 allowNull: true,
276 type: DataType.BOOLEAN,
277 defaultValue: false,
278 })
279 declare banned: boolean | null;
280
281 @Column({
282 allowNull: true,
283 type: DataType.INTEGER,
284 defaultValue: 0,
285 })
286 declare role: number;
287
288 @Column({
289 allowNull: true,
290 type: DataType.BOOLEAN,
291 defaultValue: false,
292 })
293 declare manuallyAcceptsFollows: boolean;
294
295 @Column({
296 allowNull: true,
297 type: DataType.STRING,
298 })
299 declare headerImage: string;
300
301 @Column({
302 allowNull: true,
303 type: DataType.STRING,
304 })
305 declare followersCollectionUrl: string;
306
307 @Column({
308 allowNull: true,
309 type: DataType.STRING,
310 })
311 declare followingCollectionUrl: string;
312
313 @Column({
314 allowNull: true,
315 type: DataType.STRING,
316 })
317 declare bskyInviteCode: string;
318
319 @Column({
320 allowNull: true,
321 type: DataType.INTEGER,
322 defaultValue: 0,
323 })
324 declare followerCount: number;
325
326 @Column({
327 allowNull: true,
328 type: DataType.INTEGER,
329 defaultValue: 0,
330 })
331 declare followingCount: number;
332
333 @Column({
334 allowNull: true,
335 type: DataType.BOOLEAN,
336 defaultValue: false,
337 })
338 declare disableEmailNotifications: boolean;
339
340 @Column({
341 allowNull: true,
342 type: DataType.BOOLEAN,
343 defaultValue: false,
344 })
345 declare enableBsky: boolean;
346
347 @Column({
348 allowNull: true,
349 type: DataType.STRING,
350 })
351 declare bskyAuthData: string | null;
352
353 @Column({
354 allowNull: true,
355 type: DataType.STRING,
356 })
357 declare bskyAppPassword: string | null;
358
359 @Column({
360 allowNull: true,
361 type: DataType.STRING(768),
362 })
363 declare bskyDid: string | null;
364
365 @Column({
366 allowNull: true,
367 type: DataType.DATE,
368 defaultValue: new Date(0),
369 })
370 declare lastActiveAt: Date;
371
372 @Column({
373 allowNull: true,
374 type: DataType.BOOLEAN,
375 defaultValue: false,
376 })
377 declare hideFollows: Boolean;
378
379 @Column({
380 allowNull: true,
381 type: DataType.BOOLEAN,
382 defaultValue: false,
383 })
384 declare hideProfileNotLoggedIn: Boolean;
385
386 @Column({
387 allowNull: true,
388 type: DataType.STRING,
389 })
390 declare displayUrl: string;
391
392 @HasMany(() => MfaDetails, {
393 sourceKey: "id",
394 })
395 declare mfaDetails: MfaDetails[];
396
397 @HasMany(() => Notification, {
398 foreignKey: "notifiedUserId",
399 })
400 declare incomingNotifications: Notification[];
401
402 @HasMany(() => Notification, {
403 foreignKey: "userId",
404 })
405 declare outgoingNotifications: Notification[];
406
407 @HasMany(() => Ask, {
408 foreignKey: "userAsker",
409 })
410 declare userAsker: Ask[];
411
412 @HasMany(() => Ask, {
413 foreignKey: "userAsked",
414 })
415 declare userAsked: Ask[];
416
417 @HasMany(() => Bites, {
418 foreignKey: "biterId",
419 })
420 declare hasBitten: Bites[];
421
422 @HasMany(() => Bites, {
423 foreignKey: "bittenId",
424 })
425 declare bittenBy: Bites[];
426
427 @HasMany(() => QuestionPollAnswer, {
428 sourceKey: "id",
429 })
430 declare questionPollAnswers: QuestionPollAnswer[];
431
432 @HasMany(() => EmojiReaction, {
433 sourceKey: "id",
434 })
435 declare emojiReacions: EmojiReaction[];
436
437 @HasMany(() => UserOptions, {
438 sourceKey: "id",
439 })
440 declare userOptions: UserOptions[];
441
442 @HasMany(() => PushNotificationToken, {
443 sourceKey: "id",
444 })
445 declare pushNotificationTokens: PushNotificationToken[];
446
447 @BelongsToMany(() => Emoji, () => UserEmojiRelation)
448 declare emojis: Emoji[];
449 declare setEmojis: BelongsToManySetAssociationsMixin<Emoji, string>;
450 declare removeEmojis: BelongsToManyRemoveAssociationsMixin<Emoji, string>;
451
452 @HasMany(() => Follows, {
453 foreignKey: "followerId",
454 })
455 declare followerFollows: Follows[];
456
457 @HasMany(() => Follows, {
458 foreignKey: "followedId",
459 })
460 declare followedFollows: Follows[];
461
462 @BelongsToMany(() => User, () => Follows, "followedId", "followerId")
463 declare follower: User[];
464 declare getFollower: BelongsToManyGetAssociationsMixin<User>;
465 declare removeFollower: BelongsToManyRemoveAssociationMixin<User, string>;
466
467 @BelongsToMany(() => User, () => Follows, "followerId", "followedId")
468 declare followed: User[];
469 declare getFollowed: BelongsToManyGetAssociationsMixin<User>;
470 declare removeFollowed: BelongsToManyRemoveAssociationMixin<User, string>;
471
472 @HasMany(() => Blocks, {
473 foreignKey: "blockerId",
474 })
475 declare blockerBlocks: Blocks[];
476
477 @HasMany(() => Blocks, {
478 foreignKey: "blockedId",
479 })
480 declare blockedBlocks: Blocks[];
481
482 @BelongsToMany(() => User, () => Blocks, "blockedId", "blockerId")
483 declare blocker: User[];
484 declare addBlocker: BelongsToManyAddAssociationMixin<User, string>;
485 declare removeBlocker: BelongsToManyRemoveAssociationMixin<User, string>;
486
487 @BelongsToMany(() => User, () => Blocks, "blockerId", "blockedId")
488 declare blocked: User[];
489
490 @HasMany(() => Mutes, {
491 foreignKey: "muterId",
492 })
493 declare muterMutes: Mutes[];
494
495 @HasMany(() => Mutes, {
496 foreignKey: "mutedId",
497 })
498 declare mutedMutes: Mutes[];
499
500 @BelongsToMany(() => User, () => Mutes, "mutedId", "muterId")
501 declare muter: User[];
502 declare addMuter: BelongsToManyAddAssociationMixin<User, string>;
503 declare removeMuter: BelongsToManyRemoveAssociationMixin<User, string>;
504
505 @BelongsToMany(() => User, () => Mutes, "muterId", "mutedId")
506 declare muted: User[];
507
508 @HasMany(() => ServerBlock, {
509 sourceKey: "id",
510 })
511 declare serverBlocks: ServerBlock[];
512
513 @HasMany(() => PostReport, {
514 sourceKey: "id",
515 })
516 declare postReports: PostReport[];
517
518 @HasMany(() => SilencedPost, {
519 sourceKey: "id",
520 })
521 declare silencedPosts: SilencedPost[];
522
523 @BelongsTo(() => FederatedHost, {
524 foreignKey: {
525 name: "federatedHostId",
526 allowNull: true,
527 },
528 })
529 declare federatedHost: FederatedHost;
530 declare getFederatedHost: BelongsToGetAssociationMixin<FederatedHost>;
531
532 @HasMany(() => Post, {
533 sourceKey: "id",
534 })
535 declare posts: Post[];
536 declare getPosts: HasManyGetAssociationsMixin<Post>;
537
538 @HasMany(() => Media, {
539 sourceKey: "id",
540 })
541 declare medias: Media[];
542
543 @HasMany(() => PostMentionsUserRelation, {
544 sourceKey: "id",
545 })
546 declare pMURs: PostMentionsUserRelation[];
547
548 @BelongsToMany(() => Post, () => PostMentionsUserRelation)
549 declare mentionPost: Post[];
550
551 @HasMany(() => UserBitesPostRelation, {
552 sourceKey: "id",
553 })
554 declare userBitesPostRelation: UserBitesPostRelation[];
555 declare getUserBitesPostRelation: HasManyGetAssociationsMixin<UserBitesPostRelation>;
556
557 @HasMany(() => UserLikesPostRelations, {
558 sourceKey: "id",
559 })
560 declare userLikesPostRelations: UserLikesPostRelations[];
561 declare getUserLikesPostRelations: HasManyGetAssociationsMixin<UserLikesPostRelations>;
562
563 @HasMany(() => UserBookmarkedPosts, {
564 sourceKey: "id",
565 })
566 declare userBookmarkedPosts: UserBookmarkedPosts[];
567 declare getUserBookmarkedPosts: HasManyGetAssociationsMixin<UserBookmarkedPosts>;
568
569 @HasMany(() => RemoteUserPostView, {
570 sourceKey: "id",
571 })
572 declare remoteUserPostViewList: RemoteUserPostView[];
573
574 @HasMany(() => UserFollowHashtags, {
575 sourceKey: "id",
576 })
577 declare userFollowedHashtagList: UserFollowHashtags[];
578
579 @Column({
580 allowNull: true,
581 type: DataType.BOOLEAN,
582 defaultValue: false,
583 })
584 declare selfDeleted: boolean;
585
586 @Column({
587 allowNull: true,
588 type: DataType.STRING(768),
589 })
590 declare userMigratedTo: string;
591
592 get isRemoteUser() {
593 return !!this.url.startsWith("@");
594 }
595
596 get isLocalUser() {
597 return !this.isRemoteUser;
598 }
599
600 get isBlueskyUser() {
601 return !!(this.url.split("@").length == 2 && this.bskyDid);
602 }
603
604 get isFediverseUser() {
605 return !!(this.url.split("@").length == 3 && this.remoteId);
606 }
607
608 // the username part of the handle, without the domain for both bsky and fedi
609 get shortHandle() {
610 if (this.isBlueskyUser) return this.url.split("@")[1].split(".")[0];
611
612 if (this.isFediverseUser) return this.url.split("@")[1];
613
614 return this.url;
615 }
616
617 // the username part of the handle. For bluesky also includes the domain, but for fedi it doesn't
618 get longHandle() {
619 if (this.isBlueskyUser || this.isFediverseUser)
620 return this.url.split("@")[1];
621
622 return this.url;
623 }
624
625 // the full handle regardless if it's a local user or not. Fedi format for local users and fedi users; Bsky format for bsky users
626 get fullHandle() {
627 return this.isRemoteUser
628 ? this.url
629 : `@${this.url}@${completeEnvironment.instanceUrl}`;
630 }
631
632 get fullUrl() {
633 return (
634 this.remoteId || `${completeEnvironment.frontendUrl}/blog/${this.url}`
635 );
636 }
637
638 get fullFediverseUrl() {
639 return this.isRemoteUser
640 ? this.remoteId
641 : `${completeEnvironment.frontendUrl}/fediverse/blog/${this.url}`;
642 }
643
644 get avatarFullUrl() {
645 return this.isRemoteUser
646 ? this.avatar
647 : `${completeEnvironment.mediaUrl}${this.avatar}`;
648 }
649
650 get headerImageFullUrl() {
651 return this.isRemoteUser
652 ? this.headerImage
653 : `${completeEnvironment.mediaUrl}${this.headerImage}`;
654 }
655}
656
657export function getLocalUsernameFromLocalRemoteId(remoteId: string) {
658 return remoteId
659 .split(`${completeEnvironment.instanceUrl}/fediverse/blog/`)[1]
660 .split("@")[0];
661}
662
663export function isLocalRemoteId(remoteId: string) {
664 return remoteId.startsWith(completeEnvironment.frontendUrl);
665}
666
667export interface HandleData {
668 username: string;
669 handle: string;
670 domain: string;
671 type: "fediverse" | "bluesky" | "local";
672}
673
674export function splitHandle(handleString: string): HandleData {
675 handleString = handleString.trim();
676 if (handleString.startsWith("@") && handleString.length > 3) {
677 const userData = handleString.split("@");
678 if (userData.length === 3 && userData[0] == "") {
679 const username = userData[1];
680 const domain = userData[2];
681 return {
682 handle: handleString,
683 username: username,
684 domain: domain,
685 type: "fediverse",
686 };
687 } else if (userData.length === 2 && userData[0] == "") {
688 const handle = userData[1];
689 const elements = handle.split(".");
690 const username = elements.shift() as string;
691 const domain = elements.join(".");
692 return {
693 handle: handle,
694 username: username,
695 domain: domain,
696 type: "bluesky",
697 };
698 }
699 }
700 return {
701 username: handleString,
702 handle: handleString,
703 domain: completeEnvironment.instanceUrl,
704 type: "local",
705 };
706}
707
708export function addHandlePrefix(handle: string) {
709 return handle.startsWith("@") ? handle : `@${handle}`;
710}