From e5dee021c327a4784279926f22d60d070c39f154 Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sat, 14 May 2022 16:35:16 +0300 Subject: [PATCH 01/15] Initialise models with pubSub explicitly --- app/models.js | 8 ++++---- app/models/comment.js | 3 +-- app/models/group.js | 4 ++-- app/models/post.js | 3 +-- app/models/user.js | 4 ++-- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/models.js b/app/models.js index aabaf8f7a..f6c373499 100644 --- a/app/models.js +++ b/app/models.js @@ -30,12 +30,12 @@ if (config.disableRealtime) { export const PubSub = new pubSub(pubsubAdapter); -export const User = userModel(dbAdapter); -export const Group = groupModel(dbAdapter); -export const Post = postModel(dbAdapter); +export const User = userModel(dbAdapter, PubSub); +export const Group = groupModel(dbAdapter, PubSub); +export const Post = postModel(dbAdapter, PubSub); export const Timeline = timelineModel(dbAdapter); export const Attachment = attachmentModel(dbAdapter); -export const Comment = commentModel(dbAdapter); +export const Comment = commentModel(dbAdapter, PubSub); export const ServerInfo = addServerInfoModel(dbAdapter); export const Job = addJobModel(dbAdapter); export const JobManager = addJobManagerModel(dbAdapter); diff --git a/app/models/comment.js b/app/models/comment.js index 6ca476b91..69e810928 100644 --- a/app/models/comment.js +++ b/app/models/comment.js @@ -5,13 +5,12 @@ import monitor from 'monitor-dog'; import config from 'config'; import { extractHashtags } from '../support/hashtags'; -import { PubSub as pubSub } from '../models'; import { EventService } from '../support/EventService'; import { getRoomsOfPost } from '../pubsub-listener'; import { getUpdatedUUIDs, notifyBacklinkedLater, notifyBacklinkedNow } from '../support/backlinks'; import { List } from '../support/open-lists'; -export function addModel(dbAdapter) { +export function addModel(dbAdapter, pubSub) { class Comment { static VISIBLE = 0; static DELETED = 1; diff --git a/app/models/group.js b/app/models/group.js index 4cdb91768..9f70323c5 100644 --- a/app/models/group.js +++ b/app/models/group.js @@ -1,7 +1,7 @@ -import { User, PubSub as pubSub } from '../models'; +import { User } from '../models'; import { ForbiddenException, ValidationException } from '../support/exceptions'; -export function addModel(dbAdapter) { +export function addModel(dbAdapter, pubSub) { return class Group extends User { // Groups only have 'Posts' feed static feedNames = ['Posts']; diff --git a/app/models/post.js b/app/models/post.js index 495d60a16..7ac807d5a 100644 --- a/app/models/post.js +++ b/app/models/post.js @@ -4,7 +4,6 @@ import _ from 'lodash'; import config from 'config'; import { extractHashtags } from '../support/hashtags'; -import { PubSub as pubSub } from '../models'; import { getRoomsOfPost } from '../pubsub-listener'; import { EventService } from '../support/EventService'; import { List } from '../support/open-lists'; @@ -25,7 +24,7 @@ import { /** * @param {DbAdapter} dbAdapter */ -export function addModel(dbAdapter) { +export function addModel(dbAdapter, pubSub) { class Post { id; attachments; diff --git a/app/models/user.js b/app/models/user.js index 2cea1a4df..c76b3b306 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -13,7 +13,7 @@ import config from 'config'; import { getS3 } from '../support/s3'; import { BadRequestException, NotFoundException, ValidationException } from '../support/exceptions'; -import { Attachment, Comment, Post, PubSub as pubSub } from '../models'; +import { Attachment, Comment, Post } from '../models'; import { EventService } from '../support/EventService'; import { userCooldownStart, userDataDeletionStart } from '../jobs/user-gone'; import { allExternalProviders } from '../support/ExtAuth'; @@ -38,7 +38,7 @@ export const GONE_NAMES = { [GONE_DELETED]: 'DELETED', }; -export function addModel(dbAdapter) { +export function addModel(dbAdapter, pubSub) { return class User { static PROFILE_PICTURE_SIZE_LARGE = 75; static PROFILE_PICTURE_SIZE_MEDIUM = 50; From bb37ac2a90d0cbba0ca5dc4b336b424449ba52d9 Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sat, 14 May 2022 16:44:09 +0300 Subject: [PATCH 02/15] Declare type of Koa-context --- app/freefeed-app.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/freefeed-app.ts b/app/freefeed-app.ts index bf16eb302..357025fd6 100644 --- a/app/freefeed-app.ts +++ b/app/freefeed-app.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import createDebug from 'debug'; -import Application from 'koa'; +import Application, { type DefaultState } from 'koa'; import config from 'config'; import koaBody from 'koa-body'; import methodOverride from 'koa-methodoverride'; @@ -19,10 +19,17 @@ import { originMiddleware } from './setup/initializers/origin'; import { maintenanceCheck } from './support/maintenance'; import { reportError } from './support/exceptions'; import { normalizeInputStrings } from './controllers/middlewares/normalize-input'; +import type PubsubListener from './pubsub-listener'; const env = process.env.NODE_ENV || 'development'; -class FreefeedApp extends Application { +export interface FreefeedContext { + config: typeof config; + port: number; + pubsub: PubsubListener; +} + +class FreefeedApp extends Application { constructor(options: Record | undefined = {}) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore // error in ts-definition @@ -34,7 +41,7 @@ class FreefeedApp extends Application { } this.context.config = config; - this.context.port = process.env.PORT || config.port; + this.context.port = process.env.PORT ? parseInt(process.env.PORT) : config.port; this.use( koaBody({ From f27445013e009ce80668e895fb61c85b90ddb392 Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sat, 14 May 2022 17:36:04 +0300 Subject: [PATCH 03/15] Move model-classes initialisation into registry This way we will be able to move away (in future commits) from implicit nodejs-module singleton pattern towards controllable registry of instances. Immediate reason to do this is a need to get away from the fragile import-loops in models --- app/models-registry.ts | 51 ++++++++++++++++++++++++++++++++++++++++++ app/models.js | 21 ++++------------- app/models/group.js | 9 ++++---- app/models/user.js | 9 ++++---- 4 files changed, 63 insertions(+), 27 deletions(-) create mode 100644 app/models-registry.ts diff --git a/app/models-registry.ts b/app/models-registry.ts new file mode 100644 index 000000000..e52cae6e0 --- /dev/null +++ b/app/models-registry.ts @@ -0,0 +1,51 @@ +import { type DbAdapter } from './support/DbAdapter'; +import type PubSub from './pubsub'; +import { addModel as attachmentModel } from './models/attachment'; +import { addModel as commentModel } from './models/comment'; +import { addModel as groupModel } from './models/group'; +import { addModel as postModel } from './models/post'; +import { addModel as timelineModel } from './models/timeline'; +import { addModel as userModel } from './models/user'; +import { addServerInfoModel } from './models/server-info'; +import { addJobModel, addJobManagerModel } from './models/job'; +import { + type User, + type Group, + type Post, + type Timeline, + type Attachment, + type Comment, + type ServerInfo, + type Job, + type JobManager, +} from './models'; + +export class ModelsRegistry { + readonly dbAdapter: DbAdapter; + readonly pubSub: PubSub; + + readonly User: typeof User; + readonly Group: typeof Group; + readonly Post: typeof Post; + readonly Timeline: typeof Timeline; + readonly Attachment: typeof Attachment; + readonly Comment: typeof Comment; + readonly ServerInfo: typeof ServerInfo; + readonly Job: typeof Job; + readonly JobManager: typeof JobManager; + + constructor(dbAdapter: DbAdapter, pubSub: PubSub) { + this.dbAdapter = dbAdapter; + this.pubSub = pubSub; + + this.User = userModel(this, this.dbAdapter, this.pubSub); + this.Group = groupModel(this, this.dbAdapter, this.pubSub); + this.Post = postModel(this.dbAdapter, this.pubSub); + this.Timeline = timelineModel(this.dbAdapter); + this.Attachment = attachmentModel(this.dbAdapter); + this.Comment = commentModel(this.dbAdapter, this.pubSub); + this.ServerInfo = addServerInfoModel(this.dbAdapter); + this.Job = addJobModel(this.dbAdapter); + this.JobManager = addJobManagerModel(this.dbAdapter); + } +} diff --git a/app/models.js b/app/models.js index f6c373499..4e40ecc89 100644 --- a/app/models.js +++ b/app/models.js @@ -6,15 +6,8 @@ import { connect as postgresConnection } from './setup/postgres'; import { DbAdapter } from './support/DbAdapter'; import { PubSubAdapter } from './support/PubSubAdapter'; import pubSub, { DummyPublisher } from './pubsub'; -import { addModel as attachmentModel } from './models/attachment'; -import { addModel as commentModel } from './models/comment'; -import { addModel as groupModel } from './models/group'; -import { addModel as postModel } from './models/post'; -import { addModel as timelineModel } from './models/timeline'; -import { addModel as userModel } from './models/user'; -import { addServerInfoModel } from './models/server-info'; -import { addJobModel, addJobManagerModel } from './models/job'; import { SessionTokenV1Store } from './models/auth-tokens'; +import { ModelsRegistry } from './models-registry'; // Be careful: order of exports is important. export const postgres = postgresConnection(); @@ -30,15 +23,9 @@ if (config.disableRealtime) { export const PubSub = new pubSub(pubsubAdapter); -export const User = userModel(dbAdapter, PubSub); -export const Group = groupModel(dbAdapter, PubSub); -export const Post = postModel(dbAdapter, PubSub); -export const Timeline = timelineModel(dbAdapter); -export const Attachment = attachmentModel(dbAdapter); -export const Comment = commentModel(dbAdapter, PubSub); -export const ServerInfo = addServerInfoModel(dbAdapter); -export const Job = addJobModel(dbAdapter); -export const JobManager = addJobManagerModel(dbAdapter); +const registry = new ModelsRegistry(dbAdapter, PubSub); +export const { User, Group, Post, Timeline, Attachment, Comment, ServerInfo, Job, JobManager } = + registry; export const sessionTokenV1Store = new SessionTokenV1Store(dbAdapter); diff --git a/app/models/group.js b/app/models/group.js index 9f70323c5..a46cdf3b9 100644 --- a/app/models/group.js +++ b/app/models/group.js @@ -1,8 +1,7 @@ -import { User } from '../models'; import { ForbiddenException, ValidationException } from '../support/exceptions'; -export function addModel(dbAdapter, pubSub) { - return class Group extends User { +export function addModel(registry, dbAdapter, pubSub) { + return class Group extends registry.User { // Groups only have 'Posts' feed static feedNames = ['Posts']; @@ -27,7 +26,7 @@ export function addModel(dbAdapter, pubSub) { this.username.length >= 3 && // per spec this.username.length <= 35 && // per evidence and consensus this.username.match(/^[A-Za-z0-9]+(-[a-zA-Z0-9]+)*$/) && - !User.stopList(skip_stoplist).includes(this.username); + !registry.User.stopList(skip_stoplist).includes(this.username); return valid; } @@ -93,7 +92,7 @@ export function addModel(dbAdapter, pubSub) { } if (params.hasOwnProperty('description') && params.description != this.description) { - if (!User.descriptionIsValid(params.description)) { + if (!registry.User.descriptionIsValid(params.description)) { throw new ValidationException('Description is too long'); } diff --git a/app/models/user.js b/app/models/user.js index c76b3b306..7bd14035e 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -13,7 +13,6 @@ import config from 'config'; import { getS3 } from '../support/s3'; import { BadRequestException, NotFoundException, ValidationException } from '../support/exceptions'; -import { Attachment, Comment, Post } from '../models'; import { EventService } from '../support/EventService'; import { userCooldownStart, userDataDeletionStart } from '../jobs/user-gone'; import { allExternalProviders } from '../support/ExtAuth'; @@ -38,7 +37,7 @@ export const GONE_NAMES = { [GONE_DELETED]: 'DELETED', }; -export function addModel(dbAdapter, pubSub) { +export function addModel(registry, dbAdapter, pubSub) { return class User { static PROFILE_PICTURE_SIZE_LARGE = 75; static PROFILE_PICTURE_SIZE_MEDIUM = 50; @@ -226,7 +225,7 @@ export function addModel(dbAdapter, pubSub) { attrs.timelineIds = [timelineId]; } - return new Post(attrs); + return new registry.Post(attrs); } async updateResetPasswordToken() { @@ -948,13 +947,13 @@ export function addModel(dbAdapter, pubSub) { newComment(attrs) { attrs.userId = this.id; monitor.increment('users.comments'); - return new Comment(attrs); + return new registry.Comment(attrs); } newAttachment(attrs) { attrs.userId = this.id; monitor.increment('users.attachments'); - return new Attachment(attrs); + return new registry.Attachment(attrs); } async updateProfilePicture(filePath) { From ab0ed40c2e1a4c47db73f1aa9fb4d914cb3c1fc0 Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sat, 14 May 2022 22:29:37 +0300 Subject: [PATCH 04/15] Register DBAdapter inside of registry --- app/models-registry.ts | 8 +++++--- app/models.js | 18 +++++++++++++----- app/support/DbAdapter/index.d.ts | 3 ++- app/support/DbAdapter/index.js | 3 ++- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/models-registry.ts b/app/models-registry.ts index e52cae6e0..6d4a34163 100644 --- a/app/models-registry.ts +++ b/app/models-registry.ts @@ -1,4 +1,6 @@ -import { type DbAdapter } from './support/DbAdapter'; +import type Knex from 'knex'; + +import { DbAdapter } from './support/DbAdapter'; import type PubSub from './pubsub'; import { addModel as attachmentModel } from './models/attachment'; import { addModel as commentModel } from './models/comment'; @@ -34,8 +36,8 @@ export class ModelsRegistry { readonly Job: typeof Job; readonly JobManager: typeof JobManager; - constructor(dbAdapter: DbAdapter, pubSub: PubSub) { - this.dbAdapter = dbAdapter; + constructor(database: Knex, pubSub: PubSub) { + this.dbAdapter = new DbAdapter(database, this); this.pubSub = pubSub; this.User = userModel(this, this.dbAdapter, this.pubSub); diff --git a/app/models.js b/app/models.js index 4e40ecc89..7ef334152 100644 --- a/app/models.js +++ b/app/models.js @@ -3,7 +3,6 @@ import config from 'config'; import { connect as redisConnection } from './setup/database'; import { connect as postgresConnection } from './setup/postgres'; -import { DbAdapter } from './support/DbAdapter'; import { PubSubAdapter } from './support/PubSubAdapter'; import pubSub, { DummyPublisher } from './pubsub'; import { SessionTokenV1Store } from './models/auth-tokens'; @@ -11,7 +10,6 @@ import { ModelsRegistry } from './models-registry'; // Be careful: order of exports is important. export const postgres = postgresConnection(); -export const dbAdapter = new DbAdapter(postgres); let pubsubAdapter; @@ -23,9 +21,19 @@ if (config.disableRealtime) { export const PubSub = new pubSub(pubsubAdapter); -const registry = new ModelsRegistry(dbAdapter, PubSub); -export const { User, Group, Post, Timeline, Attachment, Comment, ServerInfo, Job, JobManager } = - registry; +const registry = new ModelsRegistry(postgres, PubSub); +export const { + dbAdapter, + User, + Group, + Post, + Timeline, + Attachment, + Comment, + ServerInfo, + Job, + JobManager, +} = registry; export const sessionTokenV1Store = new SessionTokenV1Store(dbAdapter); diff --git a/app/support/DbAdapter/index.d.ts b/app/support/DbAdapter/index.d.ts index ef4427966..110946cd3 100644 --- a/app/support/DbAdapter/index.d.ts +++ b/app/support/DbAdapter/index.d.ts @@ -11,6 +11,7 @@ import { } from '../../models/auth-tokens/types'; import { SessionTokenV1 } from '../../models/auth-tokens'; import { T_EVENT_TYPE } from '../EventTypes'; +import { ModelsRegistry } from '../../models-registry'; type QueryBindings = readonly RawBinding[] | ValueDict | RawBinding; @@ -68,7 +69,7 @@ type AttachmentsStats = { }; export class DbAdapter { - constructor(connection: Knex); + constructor(connection: Knex, registry: ModelsRegistry); database: Knex & CommonDBHelpers & TrxDBHelpers; diff --git a/app/support/DbAdapter/index.js b/app/support/DbAdapter/index.js index feaa8e59a..d307a4911 100644 --- a/app/support/DbAdapter/index.js +++ b/app/support/DbAdapter/index.js @@ -37,8 +37,9 @@ import authSessionsTrait from './auth-sessions'; import backlinksTrait from './backlinks'; class DbAdapterBase { - constructor(database) { + constructor(database, registry) { this.database = withDbHelpers(database); + this.registry = registry; this.statsCache = new NodeCache({ stdTTL: 300 }); const CACHE_TTL = 60 * 60 * 24; // 24 hours From 2e0d7d39394054dae29849a552a1225958d22dfe Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sat, 14 May 2022 23:12:10 +0300 Subject: [PATCH 05/15] Teach DBAdapter to call model-classes via registry --- app/support/DbAdapter/attachments.js | 31 ++++++------ app/support/DbAdapter/auth-sessions.js | 3 +- app/support/DbAdapter/backlinks.js | 3 +- app/support/DbAdapter/comments.js | 57 +++++++++++---------- app/support/DbAdapter/feeds.js | 64 ++++++++++++------------ app/support/DbAdapter/jobs.js | 28 +++++------ app/support/DbAdapter/posts.js | 27 +++++----- app/support/DbAdapter/search.js | 3 +- app/support/DbAdapter/subscriptions.js | 3 +- app/support/DbAdapter/timelines-posts.js | 24 +++++---- app/support/DbAdapter/users.js | 56 +++++++++++---------- 11 files changed, 150 insertions(+), 149 deletions(-) diff --git a/app/support/DbAdapter/attachments.js b/app/support/DbAdapter/attachments.js index a47d1cf03..40d95a069 100644 --- a/app/support/DbAdapter/attachments.js +++ b/app/support/DbAdapter/attachments.js @@ -1,6 +1,5 @@ import validator from 'validator'; -import { Attachment } from '../../models'; import { SANITIZE_VERSION } from '../sanitize-media'; import { initObject, prepareModelPayload } from './utils'; @@ -27,14 +26,14 @@ const attachmentsTrait = (superClass) => } const attrs = await this.database('attachments').first().where('uid', id); - return initAttachmentObject(attrs); + return this.initAttachmentObject(attrs); } async getAttachmentsByIds(ids) { const responses = await this.database('attachments') .whereIn('uid', ids) .orderByRaw(`position(uid::text in '${ids.toString()}')`); - return responses.map(initAttachmentObject); + return responses.map(this.initAttachmentObject); } async listAttachments({ userId, limit, offset = 0 }) { @@ -45,7 +44,7 @@ const attachmentsTrait = (superClass) => { userId, limit, offset }, ); - return rows.map(initAttachmentObject); + return rows.map(this.initAttachmentObject); } async updateAttachment(attachmentId, payload) { @@ -60,7 +59,7 @@ const attachmentsTrait = (superClass) => .update(preparedPayload) .returning('*'); - return initAttachmentObject(row); + return this.initAttachmentObject(row); } async deleteAttachment(id) { @@ -97,7 +96,7 @@ const attachmentsTrait = (superClass) => .orderBy('ord', 'asc') .orderBy('created_at', 'asc') .where('post_id', postId); - return responses.map(initAttachmentObject); + return responses.map(this.initAttachmentObject); } async createAttachmentsSanitizeTask(userId) { @@ -133,7 +132,7 @@ const attachmentsTrait = (superClass) => order by created_at limit :limit`, { userId, sanVersion: SANITIZE_VERSION, limit }, ); - return rows.map(initAttachmentObject); + return rows.map(this.initAttachmentObject); } async getAttachmentsStats(userId) { @@ -148,6 +147,15 @@ const attachmentsTrait = (superClass) => .reduce((sum, row) => sum + row.count, 0), }; } + + initAttachmentObject = (attrs) => { + if (!attrs) { + return null; + } + + attrs = prepareModelPayload(attrs, ATTACHMENT_FIELDS, ATTACHMENT_FIELDS_MAPPING); + return initObject(this.registry.Attachment, attrs, attrs.id); + }; }; export default attachmentsTrait; @@ -165,15 +173,6 @@ function initSanitizeTaskObject(row) { }; } -export function initAttachmentObject(attrs) { - if (!attrs) { - return null; - } - - attrs = prepareModelPayload(attrs, ATTACHMENT_FIELDS, ATTACHMENT_FIELDS_MAPPING); - return initObject(Attachment, attrs, attrs.id); -} - const ATTACHMENT_COLUMNS = { createdAt: 'created_at', updatedAt: 'updated_at', diff --git a/app/support/DbAdapter/auth-sessions.js b/app/support/DbAdapter/auth-sessions.js index be5f5fe04..b1f238830 100644 --- a/app/support/DbAdapter/auth-sessions.js +++ b/app/support/DbAdapter/auth-sessions.js @@ -1,5 +1,4 @@ -import { SessionTokenV1 } from '../../models'; -import { ACTIVE } from '../../models/auth-tokens/SessionTokenV1'; +import { ACTIVE, SessionTokenV1 } from '../../models/auth-tokens/SessionTokenV1'; import { prepareModelPayload } from './utils'; diff --git a/app/support/DbAdapter/backlinks.js b/app/support/DbAdapter/backlinks.js index fb6354442..ae1ab19a1 100644 --- a/app/support/DbAdapter/backlinks.js +++ b/app/support/DbAdapter/backlinks.js @@ -1,4 +1,3 @@ -import { Comment } from '../../models'; import { extractUUIDs } from '../backlinks'; /////////////////////////////////////////////////// @@ -37,7 +36,7 @@ const backlinksTrait = (superClass) => and hide_type = :visible and not (user_id = any(:bannedByViewer)) `, - { allComments, bannedByViewer, visible: Comment.VISIBLE }, + { allComments, bannedByViewer, visible: this.registry.Comment.VISIBLE }, ); // Keep only the visible comments backlinksData = backlinksData.filter( diff --git a/app/support/DbAdapter/comments.js b/app/support/DbAdapter/comments.js index d4fac5de0..ae9b90569 100644 --- a/app/support/DbAdapter/comments.js +++ b/app/support/DbAdapter/comments.js @@ -1,6 +1,5 @@ import validator from 'validator'; -import { Comment } from '../../models'; import { toTSVector } from '../search/to-tsvector'; import { initObject, prepareModelPayload } from './utils'; @@ -49,28 +48,28 @@ const commentsTrait = (superClass) => } const attrs = await this.database('comments').first().where('uid', id); - return initCommentObject(attrs); + return this.initCommentObject(attrs); } async getCommentBySeqNumber(postId, seqNumber) { const attrs = await this.database('comments') .first() .where({ post_id: postId, seq_number: seqNumber }); - return initCommentObject(attrs); + return this.initCommentObject(attrs); } async getCommentsByIds(ids) { const responses = await this.database('comments') .orderBy('created_at', 'desc') .whereIn('uid', ids); - return responses.map((attrs) => initCommentObject(attrs)); + return responses.map((attrs) => this.initCommentObject(attrs)); } async getCommentsByIntIds(ids) { const responses = await this.database('comments') .orderBy('created_at', 'desc') .whereIn('id', ids); - return responses.map((attrs) => initCommentObject(attrs)); + return responses.map((attrs) => this.initCommentObject(attrs)); } getCommentsIdsByIntIds(intIds) { @@ -136,7 +135,7 @@ const commentsTrait = (superClass) => { postId }, ); - return rows.map(initCommentObject); + return rows.map(this.initCommentObject); } async getPostCommentsCount(postId) { @@ -161,12 +160,15 @@ const commentsTrait = (superClass) => const hiddenCommentTypes = viewer.getHiddenCommentTypes(); if (hiddenCommentTypes.length > 0) { - if (hiddenCommentTypes.includes(Comment.HIDDEN_BANNED) && bannedUsersIds.length > 0) { + if ( + hiddenCommentTypes.includes(this.registry.Comment.HIDDEN_BANNED) && + bannedUsersIds.length > 0 + ) { query = query.where('user_id', 'not in', bannedUsersIds); } const ht = hiddenCommentTypes.filter( - (t) => t !== Comment.HIDDEN_BANNED && t !== Comment.VISIBLE, + (t) => t !== this.registry.Comment.HIDDEN_BANNED && t !== this.registry.Comment.VISIBLE, ); if (ht.length > 0) { @@ -179,13 +181,13 @@ const commentsTrait = (superClass) => const comments = responses.map((comm) => { if (bannedUsersIds.includes(comm.user_id)) { comm.user_id = null; - comm.hide_type = Comment.HIDDEN_BANNED; - comm.body = Comment.hiddenBody(Comment.HIDDEN_BANNED); + comm.hide_type = this.registry.Comment.HIDDEN_BANNED; + comm.body = this.registry.Comment.hiddenBody(this.registry.Comment.HIDDEN_BANNED); } return comm; }); - return comments.map(initCommentObject); + return comments.map(this.initCommentObject); } _deletePostComments(postId) { @@ -199,7 +201,7 @@ const commentsTrait = (superClass) => postId: null, userId: null, oldUsername: null, - hideType: Comment.DELETED, + hideType: this.registry.Comment.DELETED, ...params, }; @@ -207,19 +209,22 @@ const commentsTrait = (superClass) => throw new Error(`Undefined postId of comment`); } - if (params.hideType !== Comment.DELETED && params.hideType !== Comment.HIDDEN_ARCHIVED) { + if ( + params.hideType !== this.registry.Comment.DELETED && + params.hideType !== this.registry.Comment.HIDDEN_ARCHIVED + ) { throw new Error(`Invalid hideType of comment: ${params.hideType}`); } if ( - params.hideType === Comment.HIDDEN_ARCHIVED && + params.hideType === this.registry.Comment.HIDDEN_ARCHIVED && !params.userId === null && params.oldUsername === null ) { throw new Error(`Undefined author of HIDDEN_ARCHIVED comment`); } - if (params.hideType === Comment.HIDDEN_ARCHIVED && params.body === null) { + if (params.hideType === this.registry.Comment.HIDDEN_ARCHIVED && params.body === null) { throw new Error(`Undefined body of HIDDEN_ARCHIVED comment`); } @@ -228,10 +233,10 @@ const commentsTrait = (superClass) => .insert({ post_id: params.postId, hide_type: params.hideType, - body: Comment.hiddenBody(params.hideType), + body: this.registry.Comment.hiddenBody(params.hideType), }); - if (params.hideType === Comment.HIDDEN_ARCHIVED) { + if (params.hideType === this.registry.Comment.HIDDEN_ARCHIVED) { await this.database('hidden_comments').insert({ comment_id: uid, body: params.body, @@ -242,21 +247,21 @@ const commentsTrait = (superClass) => return uid; } + + initCommentObject = (attrs) => { + if (!attrs) { + return null; + } + + attrs = prepareModelPayload(attrs, COMMENT_FIELDS, COMMENT_FIELDS_MAPPING); + return initObject(this.registry.Comment, attrs, attrs.id); + }; }; export default commentsTrait; /////////////////////////////////////////////////// -export function initCommentObject(attrs) { - if (!attrs) { - return null; - } - - attrs = prepareModelPayload(attrs, COMMENT_FIELDS, COMMENT_FIELDS_MAPPING); - return initObject(Comment, attrs, attrs.id); -} - const COMMENT_COLUMNS = { createdAt: 'created_at', updatedAt: 'updated_at', diff --git a/app/support/DbAdapter/feeds.js b/app/support/DbAdapter/feeds.js index efdd53965..e906b93f8 100644 --- a/app/support/DbAdapter/feeds.js +++ b/app/support/DbAdapter/feeds.js @@ -1,7 +1,5 @@ import validator from 'validator'; -import { Timeline, User } from '../../models'; - import { initObject, prepareModelPayload } from './utils'; import { lockByUUID, USER_SUBSCRIPTIONS } from './adv-locks'; @@ -43,7 +41,7 @@ const feedsTrait = (superClass) => const res = await this.database('feeds').where({ user_id: userId, ord: null }); const timelines = {}; - for (const name of User.feedNames) { + for (const name of this.registry.User.feedNames) { const feed = res.find((record) => record.name === name); if (feed) { @@ -69,12 +67,12 @@ const feedsTrait = (superClass) => } const attrs = await this.database('feeds').first().where('uid', id); - return initTimelineObject(attrs, params); + return this.initTimelineObject(attrs, params); } async getTimelineByIntId(id, params) { const attrs = await this.database('feeds').first().where('id', id); - return initTimelineObject(attrs, params); + return this.initTimelineObject(attrs, params); } async getTimelinesByIds(ids, params) { @@ -87,7 +85,7 @@ const feedsTrait = (superClass) => `, { ids }, ); - return rows.map((r) => initTimelineObject(r, params)); + return rows.map((r) => this.initTimelineObject(r, params)); } async getTimelinesByIntIds(ids, params) { @@ -100,7 +98,7 @@ const feedsTrait = (superClass) => `, { ids }, ); - return rows.map((r) => initTimelineObject(r, params)); + return rows.map((r) => this.initTimelineObject(r, params)); } async getTimelinesIntIdsByUUIDs(uuids) { @@ -131,7 +129,7 @@ const feedsTrait = (superClass) => .where(where) .orderBy('s.created_at', 'desc') .orderBy('s.id', 'desc'); - return records.map(initTimelineObject); + return records.map(this.initTimelineObject); } async getUserNamedFeedId(userId, name) { @@ -151,7 +149,7 @@ const feedsTrait = (superClass) => .first() .returning('uid') .where({ user_id: userId, name, ord: null }); - return initTimelineObject(response, params); + return this.initTimelineObject(response, params); } /** @@ -169,7 +167,7 @@ const feedsTrait = (superClass) => { userId, name }, ); - return rows.map((r) => initTimelineObject(r)); + return rows.map((r) => this.initTimelineObject(r)); } /** @@ -192,7 +190,7 @@ const feedsTrait = (superClass) => { userId, name }, ), }); - return initTimelineObject(row); + return this.initTimelineObject(row); } async getUserNamedFeedsIntIds(userId, names) { @@ -230,7 +228,7 @@ const feedsTrait = (superClass) => `, { userIds, name }, ); - return rows.map((r) => initTimelineObject(r, params)); + return rows.map((r) => this.initTimelineObject(r, params)); } /** @@ -314,7 +312,7 @@ const feedsTrait = (superClass) => where uid = :feedId and ord is not null returning *`, { feedId, title }, ); - return row ? initTimelineObject(row) : null; + return row ? this.initTimelineObject(row) : null; } /** @@ -354,21 +352,33 @@ const feedsTrait = (superClass) => { feedIds }, ); } + + initTimelineObject = (attrs, params) => { + if (!attrs) { + return null; + } + + const { Timeline } = this.registry; + + const FEED_FIELDS_MAPPING = { + created_at: (time) => time.toISOString(), + updated_at: (time) => time.toISOString(), + user_id: (user_id) => { + return user_id ? user_id : ''; + }, + title: (title, { name }) => + name === 'RiverOfNews' && title === null ? Timeline.defaultRiverOfNewsTitle : title, + }; + + attrs = prepareModelPayload(attrs, FEED_FIELDS, FEED_FIELDS_MAPPING); + return initObject(Timeline, attrs, attrs.id, params); + }; }; export default feedsTrait; /////////////////////////////////////////////////// -function initTimelineObject(attrs, params) { - if (!attrs) { - return null; - } - - attrs = prepareModelPayload(attrs, FEED_FIELDS, FEED_FIELDS_MAPPING); - return initObject(Timeline, attrs, attrs.id, params); -} - const FEED_COLUMNS = { createdAt: 'created_at', updatedAt: 'updated_at', @@ -392,13 +402,3 @@ const FEED_FIELDS = { title: 'title', ord: 'ord', }; - -const FEED_FIELDS_MAPPING = { - created_at: (time) => time.toISOString(), - updated_at: (time) => time.toISOString(), - user_id: (user_id) => { - return user_id ? user_id : ''; - }, - title: (title, { name }) => - name === 'RiverOfNews' && title === null ? Timeline.defaultRiverOfNewsTitle : title, -}; diff --git a/app/support/DbAdapter/jobs.js b/app/support/DbAdapter/jobs.js index ae16cf41d..f06e86642 100644 --- a/app/support/DbAdapter/jobs.js +++ b/app/support/DbAdapter/jobs.js @@ -1,5 +1,3 @@ -import { Job } from '../../models'; - import { prepareModelPayload, initObject } from './utils'; export default function jobsTrait(superClass) { @@ -15,7 +13,7 @@ export default function jobsTrait(superClass) { { name, payload, uniqKey, unlockAt: this._jobUnlockAt(unlockAt) }, ); - return initJobObject(row); + return this.initJobObject(row); } async updateJob(id, { unlockAt = 0, failure = null } = {}) { @@ -28,12 +26,12 @@ export default function jobsTrait(superClass) { } const [row] = await this.database('jobs').update(toUpdate).where({ id }).returning('*'); - return initJobObject(row); + return this.initJobObject(row); } async getJobById(id) { const row = await this.database.getRow(`select * from jobs where id = :id`, { id }); - return initJobObject(row); + return this.initJobObject(row); } async deleteJob(id) { @@ -55,7 +53,7 @@ export default function jobsTrait(superClass) { returning *`, { count, lockTime }, ); - return rows.map(initJobObject); + return rows.map(this.initJobObject); } // For testing purposes only @@ -71,7 +69,7 @@ export default function jobsTrait(superClass) { rows = await this.database.getAll(`select * from jobs order by created_at`); } - return rows.map(initJobObject); + return rows.map(this.initJobObject); } _jobUnlockAt(unlockAt) { @@ -83,16 +81,16 @@ export default function jobsTrait(superClass) { return this.database.raw('default'); } - }; -} -function initJobObject(row) { - if (!row) { - return null; - } + initJobObject = (row) => { + if (!row) { + return null; + } - row = prepareModelPayload(row, JOB_FIELDS, JOB_FIELDS_MAPPING); - return initObject(Job, row, row.id); + row = prepareModelPayload(row, JOB_FIELDS, JOB_FIELDS_MAPPING); + return initObject(this.registry.Job, row, row.id); + }; + }; } const JOB_FIELDS = { diff --git a/app/support/DbAdapter/posts.js b/app/support/DbAdapter/posts.js index 2f4dde1c7..566f96bb5 100644 --- a/app/support/DbAdapter/posts.js +++ b/app/support/DbAdapter/posts.js @@ -2,7 +2,6 @@ import _ from 'lodash'; import validator from 'validator'; import pgFormat from 'pg-format'; -import { Post } from '../../models'; import { toTSVector } from '../search/to-tsvector'; import { andJoin, initObject, orJoin, prepareModelPayload, sqlIntarrayIn, sqlNotIn } from './utils'; @@ -53,21 +52,21 @@ const postsTrait = (superClass) => } const attrs = await this.database('posts').first().where('uid', id); - return initPostObject(attrs, params); + return this.initPostObject(attrs, params); } async getPostsByIds(ids, params) { const responses = await this.database('posts') .orderBy('bumped_at', 'desc') .whereIn('uid', ids); - return responses.map((attrs) => initPostObject(attrs, params)); + return responses.map((attrs) => this.initPostObject(attrs, params)); } async getPostsByIntIds(ids) { const responses = await this.database('posts') .orderBy('bumped_at', 'desc') .whereIn('id', ids); - return responses.map((attrs) => initPostObject(attrs)); + return responses.map((attrs) => this.initPostObject(attrs)); } getPostsIdsByIntIds(intIds) { @@ -351,7 +350,7 @@ const postsTrait = (superClass) => } initRawPosts(rawPosts, params) { - return rawPosts.map((attrs) => initPostObject(attrs, params)); + return rawPosts.map((attrs) => this.initPostObject(attrs, params)); } async isPostInUserFeed(postUID, userUID, feedName) { @@ -401,21 +400,21 @@ const postsTrait = (superClass) => const adminIds = _.map(rows, 'uid'); return this.getUsersByIds(adminIds); } + + initPostObject = (attrs, params) => { + if (!attrs) { + return null; + } + + attrs = prepareModelPayload(attrs, POST_FIELDS, POST_FIELDS_MAPPING); + return initObject(this.registry.Post, attrs, attrs.id, params); + }; }; export default postsTrait; /////////////////////////////////////////////////// -export function initPostObject(attrs, params) { - if (!attrs) { - return null; - } - - attrs = prepareModelPayload(attrs, POST_FIELDS, POST_FIELDS_MAPPING); - return initObject(Post, attrs, attrs.id, params); -} - const POST_COLUMNS = { createdAt: 'created_at', updatedAt: 'updated_at', diff --git a/app/support/DbAdapter/search.js b/app/support/DbAdapter/search.js index 4da179542..dc120dc80 100644 --- a/app/support/DbAdapter/search.js +++ b/app/support/DbAdapter/search.js @@ -14,7 +14,6 @@ import { SeqTexts, } from '../search/query-tokens'; import { List } from '../open-lists'; -import { Comment } from '../../models'; import { sqlIn, sqlNotIn, sqlIntarrayIn, andJoin, orJoin } from './utils'; @@ -171,7 +170,7 @@ const searchTrait = (superClass) => // Additional restrictions for comments const commentsRestrictionSQL = useCommentsTable ? andJoin([ - pgFormat('c.hide_type=%L', Comment.VISIBLE), + pgFormat('c.hide_type=%L', this.registry.Comment.VISIBLE), sqlNotIn('c.user_id', bannedByViewer), ]) : 'true'; diff --git a/app/support/DbAdapter/subscriptions.js b/app/support/DbAdapter/subscriptions.js index 8dbda741b..3bd620b28 100644 --- a/app/support/DbAdapter/subscriptions.js +++ b/app/support/DbAdapter/subscriptions.js @@ -1,6 +1,5 @@ import pgFormat from 'pg-format'; -import { initUserObject } from './users'; import { lockByUUID, USER_SUBSCRIPTIONS } from './adv-locks'; /////////////////////////////////////////////////// @@ -78,7 +77,7 @@ const subscriptionsTrait = (superClass) => const responses = await this.database('users').whereRaw('subscribed_feed_ids && ?', [ [timelineIntId], ]); - return responses.map(initUserObject); + return responses.map(this.initUserObject); } /** diff --git a/app/support/DbAdapter/timelines-posts.js b/app/support/DbAdapter/timelines-posts.js index cfc5120b6..98fb70dcc 100644 --- a/app/support/DbAdapter/timelines-posts.js +++ b/app/support/DbAdapter/timelines-posts.js @@ -1,12 +1,11 @@ import _ from 'lodash'; import pgFormat from 'pg-format'; -import { Comment } from '../../models'; import { List } from '../open-lists'; -import { COMMENT_FIELDS, initCommentObject } from './comments'; -import { ATTACHMENT_FIELDS, initAttachmentObject } from './attachments'; -import { POST_FIELDS, initPostObject } from './posts'; +import { COMMENT_FIELDS } from './comments'; +import { ATTACHMENT_FIELDS } from './attachments'; +import { POST_FIELDS } from './posts'; import { sqlIn, sqlIntarrayIn, sqlNotIn, andJoin, orJoin } from './utils'; /////////////////////////////////////////////////// @@ -400,12 +399,15 @@ const timelinesPostsTrait = (superClass) => let hideCommentsSQL = 'true'; if (params.hiddenCommentTypes.length > 0) { - if (params.hiddenCommentTypes.includes(Comment.HIDDEN_BANNED) && !nobodyIsBanned) { + if ( + params.hiddenCommentTypes.includes(this.registry.Comment.HIDDEN_BANNED) && + !nobodyIsBanned + ) { hideCommentsSQL = sqlNotIn('user_id', bannedUsersIds); } const ht = params.hiddenCommentTypes.filter( - (t) => t !== Comment.HIDDEN_BANNED && t !== Comment.VISIBLE, + (t) => t !== this.registry.Comment.HIDDEN_BANNED && t !== this.registry.Comment.VISIBLE, ); if (ht.length > 0) { @@ -459,7 +461,7 @@ const timelinesPostsTrait = (superClass) => for (const post of postsData) { results[post.uid] = { - post: initPostObject(post), + post: this.initPostObject(post), destinations: [], attachments: [], comments: [], @@ -483,7 +485,7 @@ const timelinesPostsTrait = (superClass) => } for (const att of attData) { - results[att.post_id].attachments.push(initAttachmentObject(att)); + results[att.post_id].attachments.push(this.initAttachmentObject(att)); } for (const lk of likesData) { @@ -494,13 +496,13 @@ const timelinesPostsTrait = (superClass) => for (const comm of commentsData) { if (!nobodyIsBanned && bannedUsersIds.includes(comm.user_id)) { comm.user_id = null; - comm.hide_type = Comment.HIDDEN_BANNED; - comm.body = Comment.hiddenBody(Comment.HIDDEN_BANNED); + comm.hide_type = this.registry.Comment.HIDDEN_BANNED; + comm.body = this.registry.Comment.hiddenBody(this.registry.Comment.HIDDEN_BANNED); comm.c_likes = '0'; comm.has_own_like = null; } - const comment = initCommentObject(comm); + const comment = this.initCommentObject(comm); comment.likes = parseInt(comm.c_likes); comment.hasOwnLike = Boolean(comm.has_own_like); results[comm.post_id].comments.push(comment); diff --git a/app/support/DbAdapter/users.js b/app/support/DbAdapter/users.js index 2e13f3092..ecd5683e4 100644 --- a/app/support/DbAdapter/users.js +++ b/app/support/DbAdapter/users.js @@ -2,8 +2,6 @@ import config from 'config'; import _ from 'lodash'; import validator from 'validator'; -import { User, Group, Comment } from '../../models'; - import { initObject, prepareModelPayload } from './utils'; const usersTrait = (superClass) => @@ -170,7 +168,7 @@ const usersTrait = (superClass) => return null; } - if (!(user instanceof User)) { + if (!(user instanceof this.registry.User)) { throw new Error(`Expected User, got ${user.constructor.name}`); } @@ -181,7 +179,7 @@ const usersTrait = (superClass) => const users = await this.getFeedOwnersByIds(userIds); _.each(users, (user) => { - if (!(user instanceof User)) { + if (!(user instanceof this.registry.User)) { throw new Error(`Expected User, got ${user.constructor.name}`); } }); @@ -196,7 +194,7 @@ const usersTrait = (superClass) => return null; } - if (!(feed instanceof User)) { + if (!(feed instanceof this.registry.User)) { throw new Error(`Expected User, got ${feed.constructor.name}`); } @@ -217,7 +215,7 @@ const usersTrait = (superClass) => throw new Error(`Expected User, got ${attrs.type}`); } - return initUserObject(attrs); + return this.initUserObject(attrs); } async getUserByEmail(email) { @@ -231,7 +229,7 @@ const usersTrait = (superClass) => throw new Error(`Expected User, got ${attrs.type}`); } - return initUserObject(attrs); + return this.initUserObject(attrs); } async _getUserIntIdByUUID(userUUID) { @@ -253,15 +251,15 @@ const usersTrait = (superClass) => return null; } - return initUserObject(await this.fetchUser(id)); + return this.initUserObject(await this.fetchUser(id)); } async getFeedOwnersByIds(ids) { - return (await this.fetchUsers(ids)).map(initUserObject); + return (await this.fetchUsers(ids)).map(this.initUserObject); } async getUsersByIdsAssoc(ids) { - return _.mapValues(await this.fetchUsersAssoc(ids), initUserObject); + return _.mapValues(await this.fetchUsersAssoc(ids), this.initUserObject); } getUsersIdsByIntIds(intIds) { @@ -284,7 +282,7 @@ const usersTrait = (superClass) => } } - return initUserObject(attrs); + return this.initUserObject(attrs); } async getFeedOwnersByUsernames(usernames) { @@ -308,7 +306,7 @@ const usersTrait = (superClass) => users.push(...rows); } - return _.uniqBy(users, 'id').map(initUserObject); + return _.uniqBy(users, 'id').map(this.initUserObject); } async getGroupById(id) { @@ -318,7 +316,7 @@ const usersTrait = (superClass) => return null; } - if (!(user instanceof Group)) { + if (!(user instanceof this.registry.Group)) { throw new Error(`Expected Group, got ${user.constructor.name}`); } @@ -332,7 +330,7 @@ const usersTrait = (superClass) => return null; } - if (!(feed instanceof Group)) { + if (!(feed instanceof this.registry.Group)) { throw new Error(`Expected Group, got ${feed.constructor.name}`); } @@ -378,7 +376,7 @@ const usersTrait = (superClass) => join comments c on c.uid = h.comment_id where c.hide_type = :hideType and (h.user_id = :userId or h.old_username = :oldUsername)`; const res = await this.database.raw(sql, { - hideType: Comment.HIDDEN_ARCHIVED, + hideType: this.registry.Comment.HIDDEN_ARCHIVED, userId, oldUsername: params.old_username, }); @@ -451,42 +449,46 @@ const usersTrait = (superClass) => const users = await this.database('users') .where('type', 'user') .whereRaw(`preferences -> 'sendNotificationsDigest' = 'true'::jsonb`); - return users.map(initUserObject); + return users.map(this.initUserObject); } async getDailyBestOfDigestRecipients() { const users = await this.database('users') .where('type', 'user') .whereRaw(`preferences -> 'sendDailyBestOfDigest' = 'true'::jsonb`); - return users.map(initUserObject); + return users.map(this.initUserObject); } async getWeeklyBestOfDigestRecipients() { const users = await this.database('users') .where('type', 'user') .whereRaw(`preferences -> 'sendWeeklyBestOfDigest' = 'true'::jsonb`); - return users.map(initUserObject); + return users.map(this.initUserObject); } async deleteUser(uid) { await this.database('users').where({ uid }).delete(); await this.cacheFlushUser(uid); } + + initUserObject = (attrs) => { + if (!attrs) { + return null; + } + + attrs = prepareModelPayload(attrs, USER_FIELDS, USER_FIELDS_MAPPING); + return initObject( + attrs.type === 'group' ? this.registry.Group : this.registry.User, + attrs, + attrs.id, + ); + }; }; export default usersTrait; /////////////////////////////////////////////////// -export function initUserObject(attrs) { - if (!attrs) { - return null; - } - - attrs = prepareModelPayload(attrs, USER_FIELDS, USER_FIELDS_MAPPING); - return initObject(attrs.type === 'group' ? Group : User, attrs, attrs.id); -} - const USER_COLUMNS = { username: 'username', screenName: 'screen_name', From 22d55965d36076a75f4bd7d62a56f92594f4acee Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sat, 14 May 2022 23:20:35 +0300 Subject: [PATCH 06/15] Tweak types --- app/models.d.ts | 3 +++ app/models/attachment.js | 3 +++ app/models/comment.js | 3 +++ app/models/group.js | 3 +++ app/models/post.js | 1 + app/models/timeline.js | 3 +++ app/models/user.js | 3 +++ 7 files changed, 19 insertions(+) diff --git a/app/models.d.ts b/app/models.d.ts index 9a89ad8d6..b8a5e1b53 100644 --- a/app/models.d.ts +++ b/app/models.d.ts @@ -12,6 +12,9 @@ export const dbAdapter: DbAdapter; export const PubSub: PubSubAdapter; export class User { + static ACCEPT_DIRECTS_FROM_ALL: string; + static ACCEPT_DIRECTS_FROM_FRIENDS: string; + id: UUID; intId: number; username: string; diff --git a/app/models/attachment.js b/app/models/attachment.js index 9df7c0bba..5725699b2 100644 --- a/app/models/attachment.js +++ b/app/models/attachment.js @@ -67,6 +67,9 @@ async function mimeTypeDetect(fileName, filePath) { return mimeType; } +/** + * @returns {typeof import('../models').Attachment} + */ export function addModel(dbAdapter) { return class Attachment { constructor(params) { diff --git a/app/models/comment.js b/app/models/comment.js index 69e810928..c81b57ce3 100644 --- a/app/models/comment.js +++ b/app/models/comment.js @@ -10,6 +10,9 @@ import { getRoomsOfPost } from '../pubsub-listener'; import { getUpdatedUUIDs, notifyBacklinkedLater, notifyBacklinkedNow } from '../support/backlinks'; import { List } from '../support/open-lists'; +/** + * @returns {typeof import('../models').Comment} + */ export function addModel(dbAdapter, pubSub) { class Comment { static VISIBLE = 0; diff --git a/app/models/group.js b/app/models/group.js index a46cdf3b9..16a1fd3ac 100644 --- a/app/models/group.js +++ b/app/models/group.js @@ -1,5 +1,8 @@ import { ForbiddenException, ValidationException } from '../support/exceptions'; +/** + * @returns {typeof import('../models').Group} + */ export function addModel(registry, dbAdapter, pubSub) { return class Group extends registry.User { // Groups only have 'Posts' feed diff --git a/app/models/post.js b/app/models/post.js index 7ac807d5a..4e77bf7d0 100644 --- a/app/models/post.js +++ b/app/models/post.js @@ -23,6 +23,7 @@ import { /** * @param {DbAdapter} dbAdapter + * @returns {typeof import('../models').Post} */ export function addModel(dbAdapter, pubSub) { class Post { diff --git a/app/models/timeline.js b/app/models/timeline.js index 27e55f2de..807f89eb5 100644 --- a/app/models/timeline.js +++ b/app/models/timeline.js @@ -25,6 +25,9 @@ export const HOMEFEED_MODE_CLASSIC = 'classic'; */ export const HOMEFEED_MODE_FRIENDS_ALL_ACTIVITY = 'friends-all-activity'; +/** + * @returns {typeof import('../models').Timeline} + */ export function addModel(dbAdapter) { class Timeline { id; diff --git a/app/models/user.js b/app/models/user.js index 7bd14035e..8dff6350d 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -37,6 +37,9 @@ export const GONE_NAMES = { [GONE_DELETED]: 'DELETED', }; +/** + * @returns {typeof import('../models').User} + */ export function addModel(registry, dbAdapter, pubSub) { return class User { static PROFILE_PICTURE_SIZE_LARGE = 75; From 9d632f323be922d0773b578aa2c261cce5098658 Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sun, 15 May 2022 00:15:17 +0300 Subject: [PATCH 07/15] Teach controllers to call models via registry --- .../api/v1/AttachmentsController.js | 7 +- .../api/v1/BookmarkletController.js | 8 +- app/controllers/api/v1/CommentsController.js | 16 ++-- .../api/v1/FeedFactoriesController.js | 3 +- app/controllers/api/v1/GroupsController.js | 27 ++++--- app/controllers/api/v1/PasswordsController.js | 5 +- app/controllers/api/v1/PostsController.js | 26 ++++--- app/controllers/api/v1/UsersController.js | 76 ++++++++++++------- app/controllers/api/v2/AppTokensController.js | 14 ++-- app/controllers/api/v2/ArchivesController.js | 11 ++- .../api/v2/ArchivesStatsController.js | 4 +- .../api/v2/CommentLikesController.js | 8 +- app/controllers/api/v2/EventsController.js | 5 +- app/controllers/api/v2/ExtAuthController.js | 23 +++--- app/controllers/api/v2/GroupsController.js | 6 +- app/controllers/api/v2/HomeFeedsController.js | 19 +++-- .../api/v2/InvitationsController.js | 12 +-- app/controllers/api/v2/PostsController.js | 7 +- app/controllers/api/v2/RequestsController.js | 7 +- app/controllers/api/v2/SearchController.js | 3 +- .../api/v2/ServerInfoController.js | 3 +- app/controllers/api/v2/StatsController.js | 4 +- app/controllers/api/v2/SummaryController.js | 18 +++-- app/controllers/api/v2/TimelinesController.js | 44 +++++++---- app/controllers/api/v2/TimelinesRSS.js | 3 +- app/controllers/api/v2/UsersController.js | 18 +++-- .../middlewares/comment-access-required.js | 5 +- .../middlewares/post-access-required.js | 5 +- .../middlewares/target-user-required.js | 3 +- .../middlewares/with-auth-token.ts | 6 +- app/freefeed-app.ts | 4 + app/models.d.ts | 2 + app/models.js | 2 +- app/pubsub-listener.js | 3 +- .../integration/controllers/checkDestNames.js | 10 +-- test/integration/controllers/middlewares.js | 4 +- .../with-auth-tokens-middleware.js | 3 +- 37 files changed, 244 insertions(+), 180 deletions(-) diff --git a/app/controllers/api/v1/AttachmentsController.js b/app/controllers/api/v1/AttachmentsController.js index 4be512715..e205b2f78 100644 --- a/app/controllers/api/v1/AttachmentsController.js +++ b/app/controllers/api/v1/AttachmentsController.js @@ -6,7 +6,6 @@ import { reportError, BadRequestException, ValidationException } from '../../../ import { serializeAttachment } from '../../../serializers/v2/post'; import { serializeUsersByIds } from '../../../serializers/v2/user'; import { authRequired } from '../../middlewares'; -import { dbAdapter } from '../../../models'; import { startAttachmentsSanitizeJob } from '../../../jobs/attachments-sanitize'; export default class AttachmentsController { @@ -90,7 +89,7 @@ export default class AttachmentsController { page = Number.parseInt(qPage, 10); } - const attachments = await dbAdapter.listAttachments({ + const attachments = await ctx.modelRegistry.dbAdapter.listAttachments({ userId: user.id, limit: limit + 1, offset: limit * (page - 1), @@ -115,8 +114,8 @@ export default class AttachmentsController { async (ctx) => { const { user } = ctx.state; const [stats, task] = await Promise.all([ - dbAdapter.getAttachmentsStats(user.id), - dbAdapter.getAttachmentsSanitizeTask(user.id), + ctx.modelRegistry.dbAdapter.getAttachmentsStats(user.id), + ctx.modelRegistry.dbAdapter.getAttachmentsSanitizeTask(user.id), ]); ctx.body = { attachments: stats, diff --git a/app/controllers/api/v1/BookmarkletController.js b/app/controllers/api/v1/BookmarkletController.js index f60114652..786fd62dd 100644 --- a/app/controllers/api/v1/BookmarkletController.js +++ b/app/controllers/api/v1/BookmarkletController.js @@ -1,6 +1,6 @@ import compose from 'koa-compose'; -import { Post, Comment, AppTokenV1 } from '../../../models'; +import { AppTokenV1 } from '../../../models/auth-tokens/AppTokenV1'; import { ForbiddenException } from '../../../support/exceptions'; import { authRequired, monitored, inputSchemaRequired } from '../../middlewares'; import { show as showPost } from '../v2/PostsController'; @@ -29,7 +29,7 @@ export const create = compose([ destNames.push(author.username); } - const timelineIds = await checkDestNames(destNames, author); + const timelineIds = await checkDestNames(destNames, author, ctx.modelRegistry.dbAdapter); // Attachments if (images.length === 0 && image !== '') { @@ -47,7 +47,7 @@ export const create = compose([ }), ); - const post = new Post({ + const post = new ctx.modelRegistry.Post({ userId: author.id, body, attachments, @@ -56,7 +56,7 @@ export const create = compose([ await post.create(); if (commentBody !== '') { - const comment = new Comment({ + const comment = new ctx.modelRegistry.Comment({ body: commentBody, postId: post.id, userId: author.id, diff --git a/app/controllers/api/v1/CommentsController.js b/app/controllers/api/v1/CommentsController.js index bc97dd5e3..e0963f9c7 100644 --- a/app/controllers/api/v1/CommentsController.js +++ b/app/controllers/api/v1/CommentsController.js @@ -1,7 +1,7 @@ import compose from 'koa-compose'; import monitor from 'monitor-dog'; -import { dbAdapter, Comment, AppTokenV1 } from '../../../models'; +import { AppTokenV1 } from '../../../models/auth-tokens/AppTokenV1'; import { ForbiddenException, NotFoundException, @@ -37,7 +37,7 @@ export const create = compose([ throw new ForbiddenException('Comments disabled'); } - const comment = new Comment({ body, postId, userId: author.id }); + const comment = new ctx.modelRegistry.Comment({ body, postId, userId: author.id }); try { await comment.create(); @@ -58,13 +58,13 @@ export const update = compose([ const { user } = ctx.state; const { commentId } = ctx.params; - const comment = await dbAdapter.getCommentById(commentId); + const comment = await ctx.modelRegistry.dbAdapter.getCommentById(commentId); if (!comment) { throw new NotFoundException('Can not find comment'); } - const post = await dbAdapter.getPostById(comment.postId); + const post = await ctx.modelRegistry.dbAdapter.getPostById(comment.postId); if (!post) { // Should not be possible @@ -98,13 +98,13 @@ export const destroy = compose([ const { user } = ctx.state; const { commentId } = ctx.params; - const comment = await dbAdapter.getCommentById(commentId); + const comment = await ctx.modelRegistry.dbAdapter.getCommentById(commentId); if (!comment) { throw new NotFoundException('Can not find comment'); } - const post = await dbAdapter.getPostById(comment.postId); + const post = await ctx.modelRegistry.dbAdapter.getPostById(comment.postId); if (!post) { // Should not be possible @@ -136,6 +136,8 @@ export async function getById(ctx) { const { user } = ctx.state; const { commentId } = ctx.params; + const { dbAdapter, Comment } = ctx.modelRegistry; + const comment = await dbAdapter.getCommentById(commentId); if (!comment) { @@ -182,7 +184,7 @@ export async function getBySeqNumber(ctx) { const { postId, seqNumber } = ctx.params; const number = Number.parseInt(seqNumber, 10); - const comment = await dbAdapter.getCommentBySeqNumber( + const comment = await ctx.modelRegistry.dbAdapter.getCommentBySeqNumber( postId, Number.isFinite(number) ? number : -1, ); diff --git a/app/controllers/api/v1/FeedFactoriesController.js b/app/controllers/api/v1/FeedFactoriesController.js index ec5940778..b980b7924 100644 --- a/app/controllers/api/v1/FeedFactoriesController.js +++ b/app/controllers/api/v1/FeedFactoriesController.js @@ -1,10 +1,9 @@ -import { dbAdapter } from '../../../models'; import { UsersController, GroupsController } from '../../../controllers'; import { NotFoundException } from '../../../support/exceptions'; export default class FeedFactoriesController { static async update(ctx) { - const feed = await dbAdapter.getFeedOwnerById(ctx.params.userId); + const feed = await ctx.modelRegistry.dbAdapter.getFeedOwnerById(ctx.params.userId); if (!feed) { throw new NotFoundException(`Feed ${ctx.params.userId} is not found`); diff --git a/app/controllers/api/v1/GroupsController.js b/app/controllers/api/v1/GroupsController.js index c9039c192..99ca0214f 100644 --- a/app/controllers/api/v1/GroupsController.js +++ b/app/controllers/api/v1/GroupsController.js @@ -1,7 +1,7 @@ import _ from 'lodash'; import compose from 'koa-compose'; -import { dbAdapter, Group, AppTokenV1 } from '../../../models'; +import { AppTokenV1 } from '../../../models/auth-tokens/AppTokenV1'; import { EventService } from '../../../support/EventService'; import { BadRequestException, @@ -36,7 +36,7 @@ export default class GroupsController { 'isRestricted', ]); - const group = new Group(params); + const group = new ctx.modelRegistry.Group(params); await group.create(ctx.state.user.id, false); await EventService.onGroupCreated(ctx.state.user.intId, group.intId); @@ -63,13 +63,13 @@ export default class GroupsController { } const adminPromises = ctx.request.body.admins.map(async (username) => { - const admin = await dbAdapter.getUserByUsername(username); + const admin = await ctx.modelRegistry.dbAdapter.getUserByUsername(username); return null === admin ? false : admin; }); let admins = await Promise.all(adminPromises); admins = admins.filter(Boolean); - const group = new Group(params); + const group = new ctx.modelRegistry.Group(params); await group.create(admins[0].id, true); // starting iteration from the second admin @@ -106,7 +106,7 @@ export default class GroupsController { 'isRestricted', ]); - const group = await dbAdapter.getGroupById(ctx.params.userId); + const group = await ctx.modelRegistry.dbAdapter.getGroupById(ctx.params.userId); if (null === group) { throw new NotFoundException("Can't find group"); @@ -134,7 +134,7 @@ export default class GroupsController { return; } - const group = await dbAdapter.getGroupByUsername(ctx.params.groupName); + const group = await ctx.modelRegistry.dbAdapter.getGroupByUsername(ctx.params.groupName); if (null === group) { throw new NotFoundException(`Group "${ctx.params.groupName}" is not found`); @@ -146,7 +146,7 @@ export default class GroupsController { throw new ForbiddenException("You aren't an administrator of this group"); } - const newAdmin = await dbAdapter.getUserByUsername(ctx.params.adminName); + const newAdmin = await ctx.modelRegistry.dbAdapter.getUserByUsername(ctx.params.adminName); if (null === newAdmin) { throw new NotFoundException(`User "${ctx.params.adminName}" is not found`); @@ -251,7 +251,7 @@ export default class GroupsController { } const { groupName, userName } = ctx.params; - const group = await dbAdapter.getGroupByUsername(groupName); + const group = await ctx.modelRegistry.dbAdapter.getGroupByUsername(groupName); if (null === group) { throw new NotFoundException(`Group "${groupName}" is not found`); @@ -263,13 +263,16 @@ export default class GroupsController { throw new ForbiddenException("You aren't an administrator of this group"); } - const user = await dbAdapter.getUserByUsername(userName); + const user = await ctx.modelRegistry.dbAdapter.getUserByUsername(userName); if (null === user) { throw new NotFoundException(`User "${userName}" is not found`); } - const hasRequest = await dbAdapter.isSubscriptionRequestPresent(user.id, group.id); + const hasRequest = await ctx.modelRegistry.dbAdapter.isSubscriptionRequestPresent( + user.id, + group.id, + ); if (!hasRequest) { throw new Error('Invalid'); @@ -289,7 +292,7 @@ export default class GroupsController { } const { groupName, userName } = ctx.params; - const group = await dbAdapter.getGroupByUsername(groupName); + const group = await ctx.modelRegistry.dbAdapter.getGroupByUsername(groupName); if (null === group) { throw new NotFoundException(`Group "${groupName}" is not found`); @@ -301,7 +304,7 @@ export default class GroupsController { throw new ForbiddenException("You aren't an administrator of this group"); } - const user = await dbAdapter.getUserByUsername(userName); + const user = await ctx.modelRegistry.dbAdapter.getUserByUsername(userName); if (null === user) { throw new NotFoundException(`User "${userName}" is not found`); diff --git a/app/controllers/api/v1/PasswordsController.js b/app/controllers/api/v1/PasswordsController.js index 9907f76ed..be21802a5 100644 --- a/app/controllers/api/v1/PasswordsController.js +++ b/app/controllers/api/v1/PasswordsController.js @@ -1,4 +1,3 @@ -import { dbAdapter } from '../../../models'; import { UserMailer } from '../../../mailers'; import { NotFoundException } from '../../../support/exceptions'; @@ -12,7 +11,7 @@ export default class PasswordsController { return; } - const user = await dbAdapter.getUserByEmail(email); + const user = await ctx.modelRegistry.dbAdapter.getUserByEmail(email); if (!user?.isActive && !user?.isResumable) { throw new NotFoundException(`Invalid email address or user not found`); @@ -33,7 +32,7 @@ export default class PasswordsController { return; } - const user = await dbAdapter.getUserByResetToken(token); + const user = await ctx.modelRegistry.dbAdapter.getUserByResetToken(token); if (!user?.isActive && !user?.isResumable) { throw new NotFoundException(`Password reset token not found or has expired`); diff --git a/app/controllers/api/v1/PostsController.js b/app/controllers/api/v1/PostsController.js index 8733807b6..a7280a261 100644 --- a/app/controllers/api/v1/PostsController.js +++ b/app/controllers/api/v1/PostsController.js @@ -2,7 +2,7 @@ import _, { difference, differenceBy } from 'lodash'; import monitor from 'monitor-dog'; import compose from 'koa-compose'; -import { dbAdapter, Post, AppTokenV1 } from '../../../models'; +import { AppTokenV1 } from '../../../models'; /** @typedef {import('../../../models').User} User */ /** @typedef {import('../../../models').Timeline} Timeline */ import { @@ -34,10 +34,10 @@ export default class PostsController { } = ctx.request.body; const destNames = typeof feeds === 'string' ? [feeds] : feeds; - const timelineIds = await checkDestNames(destNames, author); + const timelineIds = await checkDestNames(destNames, author, ctx.modelRegistry.dbAdapter); if (attachments) { - const attObjects = await dbAdapter.getAttachmentsByIds(attachments); + const attObjects = await ctx.modelRegistry.dbAdapter.getAttachmentsByIds(attachments); if (attObjects.some((a) => a.userId !== author.id)) { throw new ForbiddenException('You can not use attachments created by other user'); @@ -48,7 +48,7 @@ export default class PostsController { } } - const newPost = new Post({ + const newPost = new ctx.modelRegistry.Post({ userId: author.id, body, attachments, @@ -86,9 +86,9 @@ export default class PostsController { let { destinationFeedIds } = post; if (feeds) { - const destUids = await checkDestNames(feeds, user); + const destUids = await checkDestNames(feeds, user, ctx.modelRegistry.dbAdapter); const [destFeeds, isDirect] = await Promise.all([ - dbAdapter.getTimelinesByIds(destUids), + ctx.modelRegistry.dbAdapter.getTimelinesByIds(destUids), post.isStrictlyDirect(), ]); @@ -117,7 +117,7 @@ export default class PostsController { } if (attachments) { - const attObjects = await dbAdapter.getAttachmentsByIds(attachments); + const attObjects = await ctx.modelRegistry.dbAdapter.getAttachmentsByIds(attachments); if (attObjects.some((a) => a.userId !== user.id)) { throw new ForbiddenException('You can not use attachments created by other user'); @@ -207,7 +207,9 @@ export default class PostsController { throw new ForbiddenException("You can't delete another user's post"); } - const fromFeedsAccounts = await dbAdapter.getFeedOwnersByUsernames(fromFeedsNames); + const fromFeedsAccounts = await ctx.modelRegistry.dbAdapter.getFeedOwnersByUsernames( + fromFeedsNames, + ); // All feed names should be valid { @@ -237,7 +239,9 @@ export default class PostsController { // The remover should be either a post author or admin of all fromFeeds if (post.userId !== user.id) { const isAdmins = await Promise.all( - fromFeedsAccounts.map((a) => dbAdapter.isUserAdminOfGroup(user.id, a.id)), + fromFeedsAccounts.map((a) => + ctx.modelRegistry.dbAdapter.isUserAdminOfGroup(user.id, a.id), + ), ); const invalidNames = isAdmins .map((v, i) => !v && fromFeedsAccounts[i].username) @@ -278,7 +282,7 @@ export default class PostsController { updatedBy: user, }); - const updatedPost = await dbAdapter.getPostById(post.id); + const updatedPost = await ctx.modelRegistry.dbAdapter.getPostById(post.id); postStillAvailable = await updatedPost.isVisibleFor(user); } @@ -370,7 +374,7 @@ export default class PostsController { * @param {User} author * @returns {Promise} */ -export async function checkDestNames(destNames, author) { +export async function checkDestNames(destNames, author, dbAdapter) { destNames = _.uniq(destNames.map((u) => u.toLowerCase())); const destUsers = await dbAdapter.getFeedOwnersByUsernames(destNames); const destUserNames = destUsers.map((u) => u.username); diff --git a/app/controllers/api/v1/UsersController.js b/app/controllers/api/v1/UsersController.js index 6e89b5f78..294e1bd0f 100644 --- a/app/controllers/api/v1/UsersController.js +++ b/app/controllers/api/v1/UsersController.js @@ -6,14 +6,8 @@ import compose from 'koa-compose'; import config from 'config'; import jwt from 'jsonwebtoken'; -import { - dbAdapter, - User, - Group, - AppTokenV1, - ServerInfo, - sessionTokenV1Store, -} from '../../../models'; +import { sessionTokenV1Store } from '../../../models'; +import { AppTokenV1 } from '../../../models/auth-tokens/AppTokenV1'; import { NotFoundException, ForbiddenException, @@ -51,7 +45,7 @@ export default class UsersController { static create = compose([ inputSchemaRequired(userCreateInputSchema), async (ctx) => { - const registrationOpen = await ServerInfo.isRegistrationOpen(); + const registrationOpen = await ctx.modelRegistry.ServerInfo.isRegistrationOpen(); if (!registrationOpen) { throw new TooManyRequestsException('New user registrations are temporarily suspended'); @@ -92,11 +86,15 @@ export default class UsersController { let invitation; if (invitationId) { - invitation = await dbAdapter.getInvitation(invitationId); - invitation = await validateInvitationAndSelectUsers(invitation, invitationId); + invitation = await ctx.modelRegistry.dbAdapter.getInvitation(invitationId); + invitation = await validateInvitationAndSelectUsers( + invitation, + invitationId, + ctx.modelRegistry, + ); } - const user = new User(params); + const user = new ctx.modelRegistry.User(params); await user.create(false); const safeRun = async (foo) => { @@ -113,10 +111,19 @@ export default class UsersController { extProfileData && safeRun(() => user.addOrUpdateExtProfile(extProfileData)), // Register invitation and subscribe to suggested feeds invitation && - safeRun(() => useInvitation(user, invitation, ctx.request.body.cancel_subscription)), + safeRun(() => + useInvitation( + user, + invitation, + ctx.request.body.cancel_subscription, + ctx.modelRegistry.dbAdapter, + ), + ), // Subscribe to onboarding user safeRun(async () => { - const onboardingUser = await dbAdapter.getFeedOwnerByUsername(config.onboardingUsername); + const onboardingUser = await ctx.modelRegistry.dbAdapter.getFeedOwnerByUsername( + config.onboardingUsername, + ); if (onboardingUser) { await user.subscribeTo(onboardingUser); @@ -158,11 +165,13 @@ export default class UsersController { params.password = ctx.request.body.password; } - const user = new User(params); + const user = new ctx.modelRegistry.User(params); await user.create(true); try { - const onboardingUser = await dbAdapter.getFeedOwnerByUsername(config.onboardingUsername); + const onboardingUser = await ctx.modelRegistry.dbAdapter.getFeedOwnerByUsername( + config.onboardingUsername, + ); if (null === onboardingUser) { throw new NotFoundException(`Feed "${config.onboardingUsername}" is not found`); @@ -199,7 +208,10 @@ export default class UsersController { ); } - const hasRequest = await dbAdapter.isSubscriptionRequestPresent(user.id, targetUser.id); + const hasRequest = await ctx.modelRegistry.dbAdapter.isSubscriptionRequestPresent( + user.id, + targetUser.id, + ); if (hasRequest) { throw new ForbiddenException( @@ -216,7 +228,10 @@ export default class UsersController { } const postsTimelineId = await targetUser.getPostsTimelineId(); - const isSubscribed = await dbAdapter.isUserSubscribedToTimeline(user.id, postsTimelineId); + const isSubscribed = await ctx.modelRegistry.dbAdapter.isUserSubscribedToTimeline( + user.id, + postsTimelineId, + ); if (isSubscribed) { throw new ForbiddenException(`You are already subscribed to this ${targetUser.type}`); @@ -257,13 +272,16 @@ export default class UsersController { return; } - const user = await dbAdapter.getUserByUsername(ctx.params.username); + const user = await ctx.modelRegistry.dbAdapter.getUserByUsername(ctx.params.username); if (null === user) { throw new NotFoundException(`User "${ctx.params.username}" is not found`); } - const hasRequest = await dbAdapter.isSubscriptionRequestPresent(user.id, ctx.state.user.id); + const hasRequest = await ctx.modelRegistry.dbAdapter.isSubscriptionRequestPresent( + user.id, + ctx.state.user.id, + ); if (!hasRequest) { throw new Error('Invalid'); @@ -336,7 +354,7 @@ export default class UsersController { throw new ForbiddenException('Unknown token type'); } - const user = await dbAdapter.getUserById(token.userId); + const user = await ctx.modelRegistry.dbAdapter.getUserById(token.userId); if (user?.isActive) { throw new ForbiddenException('This account is already active'); @@ -365,7 +383,7 @@ export default class UsersController { throw new ForbiddenException('User is protected'); } - const subscriberIds = await dbAdapter.getUserSubscribersIds(user.id); + const subscriberIds = await ctx.modelRegistry.dbAdapter.getUserSubscribersIds(user.id); if (user.isPrivate === '1' && viewer.id !== user.id && !subscriberIds.includes(viewer.id)) { throw new ForbiddenException('User is private'); @@ -396,17 +414,17 @@ export default class UsersController { throw new ForbiddenException('User is protected'); } - const subscriberIds = await dbAdapter.getUserSubscribersIds(user.id); + const subscriberIds = await ctx.modelRegistry.dbAdapter.getUserSubscribersIds(user.id); if (user.isPrivate === '1' && viewer.id !== user.id && !subscriberIds.includes(viewer.id)) { throw new ForbiddenException('User is private'); } - let timelines = await dbAdapter.getTimelinesUserSubscribed(user.id); + let timelines = await ctx.modelRegistry.dbAdapter.getTimelinesUserSubscribed(user.id); let timelineOwnersIds = timelines.map((t) => t.userId); // Leave only users and groups visible to viewer - const groupsVisibility = await dbAdapter.getGroupsVisibility( + const groupsVisibility = await ctx.modelRegistry.dbAdapter.getGroupsVisibility( timelineOwnersIds, viewer && viewer.id, ); @@ -533,7 +551,7 @@ export default class UsersController { const subscriber = ctx.state.user; const { username } = ctx.params; - const targetUser = await dbAdapter.getFeedOwnerByUsername(username); + const targetUser = await ctx.modelRegistry.dbAdapter.getFeedOwnerByUsername(username); if (!targetUser) { throw new NotFoundException(`User "${username}" is not found`); @@ -675,7 +693,7 @@ export default class UsersController { ]); } -async function validateInvitationAndSelectUsers(invitation, invitationId) { +async function validateInvitationAndSelectUsers(invitation, invitationId, registry) { if (!invitation) { throw new NotFoundException(`Invitation "${invitationId}" not found`); } @@ -684,6 +702,8 @@ async function validateInvitationAndSelectUsers(invitation, invitationId) { throw new ValidationException(`Somebody has already used invitation "${invitationId}"`); } + const { dbAdapter, User, Group } = registry; + const userNames = invitation.recommendations.users || []; const groupNames = invitation.recommendations.groups || []; @@ -722,7 +742,7 @@ async function validateInvitationAndSelectUsers(invitation, invitationId) { return { ...invitation, publicUsers, privateUsers, publicGroups, privateGroups }; } -async function useInvitation(newUser, invitation, cancel_subscription = false) { +async function useInvitation(newUser, invitation, cancel_subscription = false, dbAdapter) { await dbAdapter.useInvitation(invitation.secure_id); await EventService.onInvitationUsed(invitation.author, newUser.intId); diff --git a/app/controllers/api/v2/AppTokensController.js b/app/controllers/api/v2/AppTokensController.js index 1a62e9570..ca4151609 100644 --- a/app/controllers/api/v2/AppTokensController.js +++ b/app/controllers/api/v2/AppTokensController.js @@ -4,7 +4,7 @@ import { DateTime } from 'luxon'; import config from 'config'; import { authRequired, monitored, inputSchemaRequired } from '../../middlewares'; -import { AppTokenV1, dbAdapter } from '../../../models'; +import { AppTokenV1 } from '../../../models/auth-tokens/AppTokenV1'; import { ValidationException, NotFoundException, @@ -68,7 +68,7 @@ export const create = compose([ } } - const token = await dbAdapter.createAppToken({ + const token = await ctx.modelRegistry.dbAdapter.createAppToken({ userId: user.id, title: body.title, scopes: body.scopes, @@ -91,7 +91,7 @@ export const inactivate = compose([ monitored('app-tokens.inactivate'), async (ctx) => { const { user } = ctx.state; - const token = await dbAdapter.getAppTokenById(ctx.params.tokenId); + const token = await ctx.modelRegistry.dbAdapter.getAppTokenById(ctx.params.tokenId); if (!token || token.userId !== user.id) { throw new NotFoundException('Token not found'); @@ -108,7 +108,7 @@ export const reissue = compose([ monitored('app-tokens.reissue'), async (ctx) => { const { user } = ctx.state; - const token = await dbAdapter.getAppTokenById(ctx.params.tokenId); + const token = await ctx.modelRegistry.dbAdapter.getAppTokenById(ctx.params.tokenId); if (!token || token.userId !== user.id || !token.isActive) { throw new NotFoundException('Token not found'); @@ -155,7 +155,7 @@ export const update = compose([ body: { title }, }, } = ctx; - const token = await dbAdapter.getAppTokenById(ctx.params.tokenId); + const token = await ctx.modelRegistry.dbAdapter.getAppTokenById(ctx.params.tokenId); if (!token || token.userId !== user.id || !token.isActive) { throw new NotFoundException('Token not found'); @@ -175,7 +175,7 @@ export const list = compose([ state: { user }, } = ctx; - const tokens = await dbAdapter.listActiveAppTokens(user.id); + const tokens = await ctx.modelRegistry.dbAdapter.listActiveAppTokens(user.id); ctx.body = { tokens: tokens.map((t) => serializeAppToken(t)) }; }, @@ -208,7 +208,7 @@ export const activate = compose([ throw new ValidationException(`Invalid activation code, check that you entered it correctly`); } - const token = await dbAdapter.getAppTokenByActivationCode( + const token = await ctx.modelRegistry.dbAdapter.getAppTokenByActivationCode( activationCode, config.appTokens.activationCodeTTL, ); diff --git a/app/controllers/api/v2/ArchivesController.js b/app/controllers/api/v2/ArchivesController.js index 596a6939e..ff9366a3b 100644 --- a/app/controllers/api/v2/ArchivesController.js +++ b/app/controllers/api/v2/ArchivesController.js @@ -4,7 +4,6 @@ import compose from 'koa-compose'; import config from 'config'; import Mailer from '../../../../lib/mailer'; -import { dbAdapter } from '../../../models'; import { ForbiddenException, NotFoundException } from '../../../support/exceptions'; import { authRequired, monitored } from '../../middlewares'; @@ -13,7 +12,7 @@ export const restoration = compose([ monitored('archives.restoration'), async (ctx) => { const { user } = ctx.state; - const archParams = await dbAdapter.getUserArchiveParams(user.id); + const archParams = await ctx.modelRegistry.dbAdapter.getUserArchiveParams(user.id); if (!archParams) { throw new ForbiddenException('You have no archive record'); @@ -47,7 +46,7 @@ export const restoration = compose([ throw new ForbiddenException('Invalid data format'); } - await dbAdapter.startArchiveRestoration(user.id, params); + await ctx.modelRegistry.dbAdapter.startArchiveRestoration(user.id, params); await Mailer.sendMail( config.mailer.adminRecipient, @@ -66,7 +65,7 @@ export const activities = compose([ monitored('archives.activities'), async (ctx) => { const { user } = ctx.state; - const archParams = await dbAdapter.getUserArchiveParams(user.id); + const archParams = await ctx.modelRegistry.dbAdapter.getUserArchiveParams(user.id); if (!archParams) { throw new ForbiddenException('You have no archive record'); @@ -79,7 +78,7 @@ export const activities = compose([ } if (!archParams.restore_comments_and_likes) { - await dbAdapter.enableArchivedActivitiesRestoration(user.id); + await ctx.modelRegistry.dbAdapter.enableArchivedActivitiesRestoration(user.id); } ctx.status = 202; @@ -91,7 +90,7 @@ export const postByOldName = compose([ monitored('archives.postByOldName'), async (ctx) => { const { name } = ctx.params; - const postId = await dbAdapter.getPostIdByOldName(name); + const postId = await ctx.modelRegistry.dbAdapter.getPostIdByOldName(name); if (!postId) { throw new NotFoundException('Post not found'); diff --git a/app/controllers/api/v2/ArchivesStatsController.js b/app/controllers/api/v2/ArchivesStatsController.js index e86e68d87..13b449e9b 100644 --- a/app/controllers/api/v2/ArchivesStatsController.js +++ b/app/controllers/api/v2/ArchivesStatsController.js @@ -1,7 +1,5 @@ -import { dbAdapter } from '../../../models'; - export default class ArchivesStatsController { static async stats(ctx) { - ctx.body = await dbAdapter.getArchivesStats(); + ctx.body = await ctx.modelRegistry.dbAdapter.getArchivesStats(); } } diff --git a/app/controllers/api/v2/CommentLikesController.js b/app/controllers/api/v2/CommentLikesController.js index 50b7a9119..1e13c0f46 100644 --- a/app/controllers/api/v2/CommentLikesController.js +++ b/app/controllers/api/v2/CommentLikesController.js @@ -1,6 +1,5 @@ import compose from 'koa-compose'; -import { dbAdapter } from '../../../models'; import { ForbiddenException } from '../../../support/exceptions'; import { serializeUsersByIds } from '../../../serializers/v2/user'; import { authRequired, monitored, commentAccessRequired } from '../../middlewares'; @@ -55,8 +54,11 @@ export default class CommentLikesController { async (ctx) => { const { comment, user } = ctx.state; - const commentIntId = await dbAdapter._getCommentIntIdByUUID(comment.id); - const likes = await dbAdapter.getCommentLikesWithoutBannedUsers(commentIntId, user?.id); + const commentIntId = await ctx.modelRegistry.dbAdapter._getCommentIntIdByUUID(comment.id); + const likes = await ctx.modelRegistry.dbAdapter.getCommentLikesWithoutBannedUsers( + commentIntId, + user?.id, + ); const users = await serializeUsersByIds( likes.map((l) => l.userId), diff --git a/app/controllers/api/v2/EventsController.js b/app/controllers/api/v2/EventsController.js index f87bd25aa..1eaa5c028 100644 --- a/app/controllers/api/v2/EventsController.js +++ b/app/controllers/api/v2/EventsController.js @@ -1,7 +1,6 @@ import _ from 'lodash'; import compose from 'koa-compose'; -import { dbAdapter } from '../../../models'; import { ALLOWED_EVENT_TYPES } from '../../../support/EventTypes'; import { serializeEvents } from '../../../serializers/v2/event'; import { authRequired } from '../../middlewares'; @@ -43,7 +42,7 @@ export default class EventsController { } const params = getQueryParams(ctx); - const events = await dbAdapter.getUserEvents( + const events = await ctx.modelRegistry.dbAdapter.getUserEvents( ctx.state.user.intId, params.eventTypes, params.limit + 1, @@ -74,7 +73,7 @@ export default class EventsController { const { user } = ctx.state; const { notifId: eventId } = ctx.params; - const event = await dbAdapter.getEventById(eventId); + const event = await ctx.modelRegistry.dbAdapter.getEventById(eventId); if (event?.user_id !== user.intId) { throw new NotFoundException('Notification not found'); diff --git a/app/controllers/api/v2/ExtAuthController.js b/app/controllers/api/v2/ExtAuthController.js index d17bf3059..1e72011e5 100644 --- a/app/controllers/api/v2/ExtAuthController.js +++ b/app/controllers/api/v2/ExtAuthController.js @@ -19,7 +19,7 @@ import { SIGN_IN_CONTINUE, profileCache, } from '../../../support/ExtAuth'; -import { User, dbAdapter, sessionTokenV1Store } from '../../../models'; +import { sessionTokenV1Store } from '../../../models'; import { serializeUsersByIds } from '../../../serializers/v2/user'; import { authStartInputSchema, authFinishInputSchema } from './data-schemes/ext-auth'; @@ -87,7 +87,7 @@ export const authFinish = compose([ throw new NotAuthorizedException(); } - const profileUser = await User.getByExtProfile(profileData); + const profileUser = await ctx.modelRegistry.User.getByExtProfile(profileData); if (profileUser && profileUser.id !== currentUser.id) { throw new ForbiddenException( @@ -109,7 +109,7 @@ export const authFinish = compose([ pictureURL: state.profile.pictureURL, }; - const profileUser = await User.getByExtProfile(profileData); + const profileUser = await ctx.modelRegistry.User.getByExtProfile(profileData); if (profileUser) { if (!profileUser.isActive) { @@ -130,7 +130,8 @@ export const authFinish = compose([ } const emailUser = - state.profile.email && (await dbAdapter.getUserByEmail(state.profile.email)); + state.profile.email && + (await ctx.modelRegistry.dbAdapter.getUserByEmail(state.profile.email)); ctx.body = { status: emailUser ? SIGN_IN_USER_EXISTS : SIGN_IN_CONTINUE, @@ -156,7 +157,11 @@ export const authFinish = compose([ .replace(/[^a-z0-9]/gi, ''); } - ctx.body.suggestedUsername = await adaptUsername(username); + ctx.body.suggestedUsername = await adaptUsername( + username, + ctx.modelRegistry.dbAdapter, + ctx.modelRegistry.User, + ); } } } catch (err) { @@ -175,7 +180,7 @@ function serializeExtProfile(profile) { return pick(profile, ['id', 'provider', 'title', 'createdAt']); } -function isValidUsername(username) { +function isValidUsername(username, User) { return Reflect.apply(User.prototype.isValidUsername, { username }, [false]); } @@ -184,16 +189,16 @@ function isValidUsername(username) { * Return empty string if can not adapt username. * * @param {string} username - * @return {string} + * @return {Promise} */ -async function adaptUsername(username) { +async function adaptUsername(username, dbAdapter, User) { if (username.length < 3) { return ''; } // eslint-disable-next-line no-constant-condition while (true) { - if (isValidUsername(username)) { + if (isValidUsername(username, User)) { // eslint-disable-next-line no-await-in-loop const existingUser = await dbAdapter.getFeedOwnerByUsername(username); diff --git a/app/controllers/api/v2/GroupsController.js b/app/controllers/api/v2/GroupsController.js index 84679a02c..29669c375 100644 --- a/app/controllers/api/v2/GroupsController.js +++ b/app/controllers/api/v2/GroupsController.js @@ -1,6 +1,5 @@ import _ from 'lodash'; -import { dbAdapter } from '../../../models'; import { userSerializerFunction } from '../../../serializers/v2/user'; export default class GroupsController { @@ -23,7 +22,9 @@ export default class GroupsController { ]); const unconfirmedFollowerIds = await group.getSubscriptionRequestIds(); - const unconfirmedFollowers = await dbAdapter.getUsersByIds(unconfirmedFollowerIds); + const unconfirmedFollowers = await ctx.modelRegistry.dbAdapter.getUsersByIds( + unconfirmedFollowerIds, + ); const requests = unconfirmedFollowers.map(async (user) => { const request = _.pick(user, ['id', 'username', 'screenName']); request.profilePictureLargeUrl = await user.getProfilePictureLargeUrl(); @@ -41,6 +42,7 @@ export default class GroupsController { static async allGroups(ctx) { const { user: viewer } = ctx.state; const withProtected = !!viewer; + const { dbAdapter } = ctx.modelRegistry; const groups = await dbAdapter.getAllGroups({ withProtected }); ctx.body = { groups }; diff --git a/app/controllers/api/v2/HomeFeedsController.js b/app/controllers/api/v2/HomeFeedsController.js index 6728317d6..565b43e9c 100644 --- a/app/controllers/api/v2/HomeFeedsController.js +++ b/app/controllers/api/v2/HomeFeedsController.js @@ -8,7 +8,6 @@ import { NotFoundException, ForbiddenException, } from '../../../support/exceptions'; -import { dbAdapter, PubSub as pubSub } from '../../../models'; import { createHomeFeedInputSchema, @@ -50,7 +49,7 @@ export const createHomeFeed = compose([ const feed = await user.createHomeFeed(body.title); - await pubSub.updateHomeFeeds(user.id); + await ctx.modelRegistry.pubSub.updateHomeFeeds(user.id); if ('subscribedTo' in body) { await feed.updateHomeFeedSubscriptions(body.subscribedTo); @@ -71,7 +70,7 @@ export const updateHomeFeed = compose([ request: { body }, } = ctx; - const feed = await dbAdapter.getTimelineById(ctx.params.feedId); + const feed = await ctx.modelRegistry.dbAdapter.getTimelineById(ctx.params.feedId); if (!feed || feed.userId !== user.id || feed.name !== 'RiverOfNews') { throw new NotFoundException(`Home feed is not found`); @@ -88,7 +87,7 @@ export const updateHomeFeed = compose([ throw new NotFoundException(`Home feed is not found`); } - await pubSub.updateHomeFeeds(user.id); + await ctx.modelRegistry.pubSub.updateHomeFeeds(user.id); } if ('subscribedTo' in body) { @@ -109,7 +108,7 @@ export const deleteHomeFeed = compose([ request: { body }, } = ctx; - const feed = await dbAdapter.getTimelineById(ctx.params.feedId); + const feed = await ctx.modelRegistry.dbAdapter.getTimelineById(ctx.params.feedId); if (!feed || feed.userId !== user.id || feed.name !== 'RiverOfNews') { throw new NotFoundException(`Home feed is not found`); @@ -126,7 +125,7 @@ export const deleteHomeFeed = compose([ throw new NotFoundException(`Home feed is not found`); } - await pubSub.updateHomeFeeds(user.id); + await ctx.modelRegistry.pubSub.updateHomeFeeds(user.id); ctx.body = { backupFeed: params.backupFeedId }; }, @@ -142,15 +141,15 @@ export const reorderHomeFeeds = compose([ request: { body }, } = ctx; - const feeds = await dbAdapter.getTimelinesByIds(body.reorder); + const feeds = await ctx.modelRegistry.dbAdapter.getTimelinesByIds(body.reorder); if (feeds.length === 0 || feeds.some((f) => f.userId !== user.id || f.name !== 'RiverOfNews')) { throw new ForbiddenException(`These feeds cannot be reordered`); } - await dbAdapter.reorderFeeds(feeds.map((f) => f.id)); + await ctx.modelRegistry.dbAdapter.reorderFeeds(feeds.map((f) => f.id)); - await pubSub.updateHomeFeeds(user.id); + await ctx.modelRegistry.pubSub.updateHomeFeeds(user.id); await listHomeFeeds(ctx); }, @@ -192,7 +191,7 @@ export const getHomeFeedInfo = compose([ state: { user }, } = ctx; - const feed = await dbAdapter.getTimelineById(ctx.params.feedId); + const feed = await ctx.modelRegistry.dbAdapter.getTimelineById(ctx.params.feedId); if (!feed || feed.userId !== user.id || feed.name !== 'RiverOfNews') { throw new NotFoundException(`Home feed is not found`); diff --git a/app/controllers/api/v2/InvitationsController.js b/app/controllers/api/v2/InvitationsController.js index 2c9b5ae41..621ee9ff0 100644 --- a/app/controllers/api/v2/InvitationsController.js +++ b/app/controllers/api/v2/InvitationsController.js @@ -1,18 +1,18 @@ import _ from 'lodash'; -import { dbAdapter } from '../../../models'; import { NotFoundException, ValidationException } from '../../../support/exceptions'; import { userSerializerFunction } from '../../../serializers/v2/user'; export default class InvitationsController { static async getInvitation(ctx) { - const invitation = await dbAdapter.getInvitation(ctx.params.secureId); + const invitation = await ctx.modelRegistry.dbAdapter.getInvitation(ctx.params.secureId); if (!invitation) { throw new NotFoundException(`Can't find invitation '${ctx.params.secureId}'`); } const invitationUsers = await serializeInvitationUsers( + ctx.modelRegistry.dbAdapter, invitation.recommendations.users, invitation.recommendations.groups, invitation.author, @@ -34,9 +34,9 @@ export default class InvitationsController { return; } - await validateInvitation(ctx.request); + await validateInvitation(ctx.request, ctx.modelRegistry.dbAdapter); - const [invitationId] = await dbAdapter.createInvitation( + const [invitationId] = await ctx.modelRegistry.dbAdapter.createInvitation( ctx.state.user.intId, ctx.request.body.message, ctx.request.body.lang, @@ -50,7 +50,7 @@ export default class InvitationsController { } } -async function serializeInvitationUsers(userNames, groupNames, authorIntId) { +async function serializeInvitationUsers(dbAdapter, userNames, groupNames, authorIntId) { userNames = userNames || []; groupNames = groupNames || []; @@ -81,7 +81,7 @@ async function serializeInvitationUsers(userNames, groupNames, authorIntId) { }; } -async function validateInvitation(request) { +async function validateInvitation(request, dbAdapter) { const users = await dbAdapter.getFeedOwnersByUsernames(request.body.users || []); const groups = await dbAdapter.getFeedOwnersByUsernames(request.body.groups || []); diff --git a/app/controllers/api/v2/PostsController.js b/app/controllers/api/v2/PostsController.js index 96e36daec..c9770c639 100644 --- a/app/controllers/api/v2/PostsController.js +++ b/app/controllers/api/v2/PostsController.js @@ -2,7 +2,6 @@ import config from 'config'; import _, { difference } from 'lodash'; import compose from 'koa-compose'; -import { dbAdapter } from '../../../models'; import { serializeSinglePost, serializeFeed } from '../../../serializers/v2/post'; import { authRequired, @@ -30,6 +29,7 @@ export const show = compose([ export const opengraph = compose([ monitored('posts.opengraph-v2'), async (ctx) => { + const { dbAdapter } = ctx.modelRegistry; const post = await dbAdapter.getPostById(ctx.params.postId); // OpenGraph is available for public posts that are not protected @@ -113,7 +113,10 @@ export const getByIds = compose([ const foldComments = ctx.request.query.maxComments !== 'all'; const foldLikes = ctx.request.query.maxLikes !== 'all'; - const visiblePostIds = await dbAdapter.selectPostsVisibleByUser(postIds, viewer?.id); + const visiblePostIds = await ctx.modelRegistry.dbAdapter.selectPostsVisibleByUser( + postIds, + viewer?.id, + ); ctx.body = await serializeFeed(visiblePostIds, viewer?.id, null, { foldComments, foldLikes }); const postsFound = ctx.body.posts.map((p) => p.id); diff --git a/app/controllers/api/v2/RequestsController.js b/app/controllers/api/v2/RequestsController.js index e408acd5b..9f55ff576 100644 --- a/app/controllers/api/v2/RequestsController.js +++ b/app/controllers/api/v2/RequestsController.js @@ -1,4 +1,3 @@ -import { dbAdapter } from '../../../models'; import { EventService } from '../../../support/EventService'; import { NotFoundException } from '../../../support/exceptions'; @@ -11,13 +10,15 @@ export default class RequestsController { } const followedFeedOwnerName = ctx.params.followedUserName; - const followedFeedOwner = await dbAdapter.getFeedOwnerByUsername(followedFeedOwnerName); + const followedFeedOwner = await ctx.modelRegistry.dbAdapter.getFeedOwnerByUsername( + followedFeedOwnerName, + ); if (null === followedFeedOwner) { throw new NotFoundException(`Feed owner "${followedFeedOwnerName}" is not found`); } - const subscriptionRequestFound = await dbAdapter.isSubscriptionRequestPresent( + const subscriptionRequestFound = await ctx.modelRegistry.dbAdapter.isSubscriptionRequestPresent( ctx.state.user.id, followedFeedOwner.id, ); diff --git a/app/controllers/api/v2/SearchController.js b/app/controllers/api/v2/SearchController.js index c02cd9a9b..483f9de32 100644 --- a/app/controllers/api/v2/SearchController.js +++ b/app/controllers/api/v2/SearchController.js @@ -1,6 +1,5 @@ import compose from 'koa-compose'; -import { dbAdapter } from '../../../models'; import { serializeFeed } from '../../../serializers/v2/post'; import { monitored } from '../../middlewares'; @@ -33,7 +32,7 @@ export default class SearchController { } const postIds = query - ? await dbAdapter.search(query, { + ? await ctx.modelRegistry.dbAdapter.search(query, { viewerId: user && user.id, limit: limit + 1, offset, diff --git a/app/controllers/api/v2/ServerInfoController.js b/app/controllers/api/v2/ServerInfoController.js index 87894ee51..96f367474 100644 --- a/app/controllers/api/v2/ServerInfoController.js +++ b/app/controllers/api/v2/ServerInfoController.js @@ -1,7 +1,6 @@ import config from 'config'; import { version } from '../../../../package.json'; -import { ServerInfo } from '../../../models'; import { allExternalProviders } from '../../../support/ExtAuth'; export async function serverInfo(ctx) { @@ -10,7 +9,7 @@ export async function serverInfo(ctx) { title, brand, })); - const registrationOpen = await ServerInfo.isRegistrationOpen(); + const registrationOpen = await ctx.modelRegistry.ServerInfo.isRegistrationOpen(); ctx.body = { version, registrationOpen, diff --git a/app/controllers/api/v2/StatsController.js b/app/controllers/api/v2/StatsController.js index 3232dc1aa..e10329eeb 100644 --- a/app/controllers/api/v2/StatsController.js +++ b/app/controllers/api/v2/StatsController.js @@ -1,7 +1,5 @@ import moment from 'moment'; -import { dbAdapter } from '../../../models'; - export default class StatsController { static async stats(ctx) { const MAX_STATS_PERIOD = 365 * 2; // 2 years @@ -53,7 +51,7 @@ export default class StatsController { throw new Error(`ERROR: the requested period is too long`); } - const stats_res = await dbAdapter.getStats( + const stats_res = await ctx.modelRegistry.dbAdapter.getStats( data, start.format(`YYYY-MM-DD`), end.format(`YYYY-MM-DD`), diff --git a/app/controllers/api/v2/SummaryController.js b/app/controllers/api/v2/SummaryController.js index e97c3d4f5..97788998d 100644 --- a/app/controllers/api/v2/SummaryController.js +++ b/app/controllers/api/v2/SummaryController.js @@ -1,6 +1,5 @@ import compose from 'koa-compose'; -import { dbAdapter } from '../../../models'; import { serializeFeed } from '../../../serializers/v2/post'; import { monitored, authRequired, targetUserRequired } from '../../middlewares'; @@ -26,10 +25,12 @@ export const generalSummary = compose([ // Get timelines that forms a "RiverOfNews" of current user const homeFeed = await currentUser.getRiverOfNewsTimeline(); - ({ destinations, activities } = await dbAdapter.getSubscriprionsIntIds(homeFeed)); + ({ destinations, activities } = await ctx.modelRegistry.dbAdapter.getSubscriprionsIntIds( + homeFeed, + )); // Get posts current user subscribed to - const foundPostsIds = await dbAdapter.getSummaryPostsIds( + const foundPostsIds = await ctx.modelRegistry.dbAdapter.getSummaryPostsIds( currentUser.id, days, destinations, @@ -52,10 +53,17 @@ export const userSummary = compose([ const currentUserId = ctx.state.user ? ctx.state.user.id : null; // Get timeline "Posts" of target user - const [timelineIntId] = await dbAdapter.getUserNamedFeedsIntIds(targetUser.id, ['Posts']); + const [timelineIntId] = await ctx.modelRegistry.dbAdapter.getUserNamedFeedsIntIds( + targetUser.id, + ['Posts'], + ); // Get posts authored by target user, and provide current user (the reader) for filtering - const foundPostsIds = await dbAdapter.getSummaryPostsIds(currentUserId, days, [timelineIntId]); + const foundPostsIds = await ctx.modelRegistry.dbAdapter.getSummaryPostsIds( + currentUserId, + days, + [timelineIntId], + ); ctx.body = await serializeFeed(foundPostsIds, currentUserId, null, { isLastPage: true }); }, diff --git a/app/controllers/api/v2/TimelinesController.js b/app/controllers/api/v2/TimelinesController.js index 96a2e839e..6153e70ad 100644 --- a/app/controllers/api/v2/TimelinesController.js +++ b/app/controllers/api/v2/TimelinesController.js @@ -5,11 +5,10 @@ import compose from 'koa-compose'; import config from 'config'; import { - dbAdapter, HOMEFEED_MODE_CLASSIC, HOMEFEED_MODE_FRIENDS_ALL_ACTIVITY, HOMEFEED_MODE_FRIENDS_ONLY, -} from '../../../models'; +} from '../../../models/timeline'; import { serializeFeed } from '../../../serializers/v2/post'; import { monitored, authRequired, targetUserRequired } from '../../middlewares'; import { NotFoundException } from '../../../support/exceptions'; @@ -26,7 +25,11 @@ export const bestOf = compose([ const offset = parseInt(ctx.request.query.offset, 10) || 0; const limit = parseInt(ctx.request.query.limit, 10) || DEFAULT_LIMIT; - const foundPostsIds = await dbAdapter.bestPostsIds(ctx.state.user, offset, limit + 1); + const foundPostsIds = await ctx.modelRegistry.dbAdapter.bestPostsIds( + ctx.state.user, + offset, + limit + 1, + ); const isLastPage = foundPostsIds.length <= limit; if (!isLastPage) { @@ -63,16 +66,19 @@ export const ownTimeline = (feedName, params = {}) => let timeline; if (ctx.params.feedId) { - timeline = await dbAdapter.getTimelineById(ctx.params.feedId); + timeline = await ctx.modelRegistry.dbAdapter.getTimelineById(ctx.params.feedId); } else { - timeline = await dbAdapter.getUserNamedFeed(user.id, feedName); + timeline = await ctx.modelRegistry.dbAdapter.getUserNamedFeed(user.id, feedName); } if (!timeline || timeline.userId !== user.id || timeline.name !== feedName) { throw new NotFoundException(`Timeline is not found`); } - ctx.body = await genericTimeline(timeline, user.id, { ...params, ...getCommonParams(ctx) }); + ctx.body = await genericTimeline(ctx.modelRegistry.dbAdapter, timeline, user.id, { + ...params, + ...getCommonParams(ctx), + }); }, ]); @@ -82,11 +88,16 @@ export const userTimeline = (feedName) => monitored(`timelines.${feedName.toLowerCase()}-v2`), async (ctx) => { const { targetUser, user: viewer } = ctx.state; - const timeline = await dbAdapter.getUserNamedFeed(targetUser.id, feedName); - ctx.body = await genericTimeline(timeline, viewer ? viewer.id : null, { - withoutDirects: feedName !== 'Posts', - ...getCommonParams(ctx), - }); + const timeline = await ctx.modelRegistry.dbAdapter.getUserNamedFeed(targetUser.id, feedName); + ctx.body = await genericTimeline( + ctx.modelRegistry.dbAdapter, + timeline, + viewer ? viewer.id : null, + { + withoutDirects: feedName !== 'Posts', + ...getCommonParams(ctx), + }, + ); }, ]); @@ -94,7 +105,12 @@ export const everything = compose([ monitored(`timelines.everything`), async (ctx) => { const { user: viewer } = ctx.state; - ctx.body = await genericTimeline(null, viewer ? viewer.id : null, getCommonParams(ctx)); + ctx.body = await genericTimeline( + ctx.modelRegistry.dbAdapter, + null, + viewer ? viewer.id : null, + getCommonParams(ctx), + ); }, ]); @@ -102,7 +118,7 @@ export const metatags = compose([ monitored(`timelines-metatags`), async (ctx) => { const { username } = ctx.params; - const targetUser = await dbAdapter.getFeedOwnerByUsername(username); + const targetUser = await ctx.modelRegistry.dbAdapter.getFeedOwnerByUsername(username); if (!targetUser || !targetUser.isActive) { ctx.body = ''; @@ -176,7 +192,7 @@ function getCommonParams(ctx, defaultSort = ORD_UPDATED) { return { limit, offset, sort, homefeedMode, withMyPosts, createdBefore, createdAfter }; } -async function genericTimeline(timeline = null, viewerId = null, params = {}) { +async function genericTimeline(dbAdapter, timeline = null, viewerId = null, params = {}) { params = { limit: 30, offset: 0, diff --git a/app/controllers/api/v2/TimelinesRSS.js b/app/controllers/api/v2/TimelinesRSS.js index 2d6b876fd..9cd1bd506 100644 --- a/app/controllers/api/v2/TimelinesRSS.js +++ b/app/controllers/api/v2/TimelinesRSS.js @@ -5,7 +5,6 @@ import { escape as htmlEscape } from 'lodash'; import compose from 'koa-compose'; import builder from 'xmlbuilder'; -import { dbAdapter } from '../../../models'; import { extractTitle, textToHTML } from '../../../support/rss-text-parser'; import { monitored } from '../../middlewares'; import { serializeComment } from '../../../serializers/v2/post'; @@ -223,7 +222,7 @@ async function postItemMaker(postId, data, ctx) { async function loadAllComments(postId, ctx) { const { user: viewer } = ctx.state; - const [postWithStuff] = await dbAdapter.getPostsWithStuffByIds( + const [postWithStuff] = await ctx.modelRegistry.dbAdapter.getPostsWithStuffByIds( [postId], viewer ? viewer.id : null, { foldComments: false }, diff --git a/app/controllers/api/v2/UsersController.js b/app/controllers/api/v2/UsersController.js index 9982b4dcd..e4b8abe57 100644 --- a/app/controllers/api/v2/UsersController.js +++ b/app/controllers/api/v2/UsersController.js @@ -2,7 +2,6 @@ import compose from 'koa-compose'; import _ from 'lodash'; import monitor from 'monitor-dog'; -import { dbAdapter, PubSub as pubSub } from '../../../models'; import { serializeSelfUser, serializeUsersByIds, @@ -33,7 +32,9 @@ export default class UsersController { const timer = monitor.timer('users.unread-directs'); try { - const unreadDirectsNumber = await dbAdapter.getUnreadDirectsNumber(ctx.state.user.id); + const unreadDirectsNumber = await ctx.modelRegistry.dbAdapter.getUnreadDirectsNumber( + ctx.state.user.id, + ); ctx.body = { unread: unreadDirectsNumber }; monitor.increment('users.unread-directs-requests'); } finally { @@ -48,7 +49,9 @@ export default class UsersController { return; } - const unreadNotificationsNumber = await dbAdapter.getUnreadEventsNumber(ctx.state.user.id); + const unreadNotificationsNumber = await ctx.modelRegistry.dbAdapter.getUnreadEventsNumber( + ctx.state.user.id, + ); ctx.body = { unread: unreadNotificationsNumber }; } @@ -59,8 +62,8 @@ export default class UsersController { return; } - await dbAdapter.markAllDirectsAsRead(ctx.state.user.id); - await pubSub.updateUnreadDirects(ctx.state.user.id); + await ctx.modelRegistry.dbAdapter.markAllDirectsAsRead(ctx.state.user.id); + await ctx.modelRegistry.pubSub.updateUnreadDirects(ctx.state.user.id); ctx.body = { message: `Directs are now marked as read for ${ctx.state.user.id}` }; } @@ -71,8 +74,8 @@ export default class UsersController { return; } - await dbAdapter.markAllEventsAsRead(ctx.state.user.id); - await pubSub.updateUnreadNotifications(ctx.state.user.intId); + await ctx.modelRegistry.dbAdapter.markAllEventsAsRead(ctx.state.user.id); + await ctx.modelRegistry.pubSub.updateUnreadNotifications(ctx.state.user.intId); ctx.body = { message: `Notifications are now marked as read for ${ctx.state.user.id}` }; } @@ -85,6 +88,7 @@ export default class UsersController { async (ctx) => { const { state: { user, authToken }, + modelRegistry: { dbAdapter }, } = ctx; const [ diff --git a/app/controllers/middlewares/comment-access-required.js b/app/controllers/middlewares/comment-access-required.js index 2c564c703..ce77c5426 100644 --- a/app/controllers/middlewares/comment-access-required.js +++ b/app/controllers/middlewares/comment-access-required.js @@ -3,7 +3,6 @@ import { NotFoundException, ForbiddenException, } from '../../support/exceptions'; -import { dbAdapter, Comment } from '../../models'; import { postAccessRequired } from './post-access-required'; @@ -23,7 +22,7 @@ export function commentAccessRequired() { } const { commentId } = ctx.params; - const comment = await dbAdapter.getCommentById(commentId); + const comment = await ctx.modelRegistry.dbAdapter.getCommentById(commentId); if (!comment) { throw new NotFoundException("Can't find comment"); @@ -41,7 +40,7 @@ export function commentAccessRequired() { throw new ForbiddenException('You have banned by the author of this comment'); } - if (comment.hideType !== Comment.VISIBLE) { + if (comment.hideType !== ctx.modelRegistry.Comment.VISIBLE) { throw new ForbiddenException(`You don't have access to this comment`); } diff --git a/app/controllers/middlewares/post-access-required.js b/app/controllers/middlewares/post-access-required.js index 0f63d39a8..ccb4b7746 100644 --- a/app/controllers/middlewares/post-access-required.js +++ b/app/controllers/middlewares/post-access-required.js @@ -3,7 +3,6 @@ import { NotFoundException, ServerErrorException, } from '../../support/exceptions'; -import { dbAdapter } from '../../models'; export function postAccessRequired(map = { postId: 'post' }) { return async (ctx, next) => { @@ -20,8 +19,8 @@ export function postAccessRequired(map = { postId: 'post' }) { } const { [key]: postId } = ctx.params; - const post = await dbAdapter.getPostById(postId); - const author = post ? await dbAdapter.getUserById(post.userId) : null; + const post = await ctx.modelRegistry.dbAdapter.getPostById(postId); + const author = post ? await ctx.modelRegistry.dbAdapter.getUserById(post.userId) : null; if (!post || !author.isActive) { throw notFound(); diff --git a/app/controllers/middlewares/target-user-required.js b/app/controllers/middlewares/target-user-required.js index 43eba5036..c148106c4 100644 --- a/app/controllers/middlewares/target-user-required.js +++ b/app/controllers/middlewares/target-user-required.js @@ -1,5 +1,4 @@ import { NotFoundException, ServerErrorException } from '../../support/exceptions'; -import { dbAdapter } from '../../models'; /** * Checks existene of all users/groups mentioned in ctx.params @@ -22,7 +21,7 @@ export function targetUserRequired(mapping = { username: 'targetUser' }) { } const { [key]: username } = ctx.params; - const targetUser = await dbAdapter.getFeedOwnerByUsername(username); + const targetUser = await ctx.modelRegistry.dbAdapter.getFeedOwnerByUsername(username); if (!targetUser) { throw new NotFoundException(`User "${username}" is not found`); diff --git a/app/controllers/middlewares/with-auth-token.ts b/app/controllers/middlewares/with-auth-token.ts index 6beb1e342..a88b7b452 100644 --- a/app/controllers/middlewares/with-auth-token.ts +++ b/app/controllers/middlewares/with-auth-token.ts @@ -5,8 +5,8 @@ import config from 'config'; import { Context, Next } from 'koa'; import { NotAuthorizedException } from '../../support/exceptions'; -import { authDebugError, AuthToken, SessionTokenV1 } from '../../models/auth-tokens'; -import { AppTokenV1, dbAdapter, sessionTokenV1Store } from '../../models'; +import { authDebugError, AuthToken, SessionTokenV1, AppTokenV1 } from '../../models/auth-tokens'; +import { sessionTokenV1Store } from '../../models'; import { Nullable } from '../../support/types'; declare module 'jsonwebtoken' { @@ -53,7 +53,7 @@ export async function withAuthToken(ctx: Context, next: Next) { authToken = await sessionTokenV1Store.getById(payload.id!); } else if (payload.type === AppTokenV1.TYPE) { // Application token v1 - authToken = await dbAdapter.getAppTokenById(payload.id!); + authToken = await ctx.modelRegistry.dbAdapter.getAppTokenById(payload.id!); } else { authToken = null; } diff --git a/app/freefeed-app.ts b/app/freefeed-app.ts index 357025fd6..a5c7b0781 100644 --- a/app/freefeed-app.ts +++ b/app/freefeed-app.ts @@ -14,12 +14,14 @@ import koaStatic from 'koa-static'; import { version as serverVersion } from '../package.json'; +import { type ModelsRegistry } from './models-registry'; import { koaServerTiming } from './support/koa-server-timing'; import { originMiddleware } from './setup/initializers/origin'; import { maintenanceCheck } from './support/maintenance'; import { reportError } from './support/exceptions'; import { normalizeInputStrings } from './controllers/middlewares/normalize-input'; import type PubsubListener from './pubsub-listener'; +import { registry } from './models'; const env = process.env.NODE_ENV || 'development'; @@ -27,6 +29,7 @@ export interface FreefeedContext { config: typeof config; port: number; pubsub: PubsubListener; + modelRegistry: ModelsRegistry; } class FreefeedApp extends Application { @@ -42,6 +45,7 @@ class FreefeedApp extends Application { this.context.config = config; this.context.port = process.env.PORT ? parseInt(process.env.PORT) : config.port; + this.context.modelRegistry = registry; this.use( koaBody({ diff --git a/app/models.d.ts b/app/models.d.ts index b8a5e1b53..d1427df5e 100644 --- a/app/models.d.ts +++ b/app/models.d.ts @@ -6,10 +6,12 @@ import { GONE_NAMES } from './models/user'; import { Nullable, UUID } from './support/types'; import { SessionTokenV1Store } from './models/auth-tokens'; import { List } from './support/open-lists'; +import { ModelsRegistry } from './models-registry'; export const postgres: Knex; export const dbAdapter: DbAdapter; export const PubSub: PubSubAdapter; +export const registry: ModelsRegistry; export class User { static ACCEPT_DIRECTS_FROM_ALL: string; diff --git a/app/models.js b/app/models.js index 7ef334152..38d7f5b9c 100644 --- a/app/models.js +++ b/app/models.js @@ -21,7 +21,7 @@ if (config.disableRealtime) { export const PubSub = new pubSub(pubsubAdapter); -const registry = new ModelsRegistry(postgres, PubSub); +export const registry = new ModelsRegistry(postgres, PubSub); export const { dbAdapter, User, diff --git a/app/pubsub-listener.js b/app/pubsub-listener.js index 3f7c6c604..572254776 100644 --- a/app/pubsub-listener.js +++ b/app/pubsub-listener.js @@ -22,7 +22,7 @@ import createDebug from 'debug'; import Raven from 'raven'; import config from 'config'; -import { dbAdapter, Comment } from './models'; +import { registry, dbAdapter, Comment } from './models'; import { eventNames } from './support/PubSubAdapter'; import { List } from './support/open-lists'; import { withAuthToken } from './controllers/middlewares/with-auth-token'; @@ -918,6 +918,7 @@ async function getAuthUserId(jwtToken, socket) { }, method: 'WS', state: { matchedRoute: '*' }, + modelRegistry: registry, }; await withAuthToken(ctx, () => null); diff --git a/test/integration/controllers/checkDestNames.js b/test/integration/controllers/checkDestNames.js index 372c97c61..383291a82 100644 --- a/test/integration/controllers/checkDestNames.js +++ b/test/integration/controllers/checkDestNames.js @@ -4,7 +4,7 @@ import expect from 'unexpected'; import { zipObject } from 'lodash'; import cleanDB from '../../dbCleaner'; -import { User } from '../../../app/models'; +import { User, dbAdapter } from '../../../app/models'; import { checkDestNames } from '../../../app/controllers/api/v1/PostsController'; describe('checkDestNames function', () => { @@ -34,22 +34,22 @@ describe('checkDestNames function', () => { const directFeedIds = await Promise.all( [users.luna, users.mars, users.venus].map((u) => u.getDirectsTimelineId()), ); - const feedIds = await checkDestNames(['mars', 'venus'], users.luna); + const feedIds = await checkDestNames(['mars', 'venus'], users.luna, dbAdapter); await expect(feedIds.sort(), 'to satisfy', directFeedIds.sort()); }); it('should not allow to Luna to send direct to Jupiter', async () => { - const call = checkDestNames(['mars', 'venus', 'jupiter'], users.luna); + const call = checkDestNames(['mars', 'venus', 'jupiter'], users.luna, dbAdapter); await expect(call, 'to be rejected with', /jupiter/); }); it('should not allow to Luna to send direct to Saturn', async () => { - const call = checkDestNames(['mars', 'venus', 'saturn'], users.luna); + const call = checkDestNames(['mars', 'venus', 'saturn'], users.luna, dbAdapter); await expect(call, 'to be rejected with', /saturn/); }); it('should not allow to Jupiter to send direct to Luna and Mars', async () => { - const call = checkDestNames(['luna', 'mars', 'venus'], users.jupiter); + const call = checkDestNames(['luna', 'mars', 'venus'], users.jupiter, dbAdapter); await expect(call, 'to be rejected with', /luna/).and('to be rejected with', /mars/); }); }); diff --git a/test/integration/controllers/middlewares.js b/test/integration/controllers/middlewares.js index 9e7947373..6a9fd947f 100644 --- a/test/integration/controllers/middlewares.js +++ b/test/integration/controllers/middlewares.js @@ -12,7 +12,7 @@ import { inputSchemaRequired, monitored, } from '../../../app/controllers/middlewares'; -import { User, Post } from '../../../app/models'; +import { User, Post, registry } from '../../../app/models'; const expect = unexpected.clone(); expect.use(unexpectedSinon); @@ -40,6 +40,7 @@ describe('Controller middlewares', () => { ctx = { params: { postId: post.id }, state: {}, + modelRegistry: registry, }; }); @@ -202,6 +203,7 @@ describe('Controller middlewares', () => { ctx = { params: { postId: post.id }, state: {}, + modelRegistry: registry, }; }); diff --git a/test/integration/controllers/with-auth-tokens-middleware.js b/test/integration/controllers/with-auth-tokens-middleware.js index 0a8ceeab1..5ced69dde 100644 --- a/test/integration/controllers/with-auth-tokens-middleware.js +++ b/test/integration/controllers/with-auth-tokens-middleware.js @@ -7,7 +7,7 @@ import { spy } from 'sinon'; import config from 'config'; import cleanDB from '../../dbCleaner'; -import { User, dbAdapter, sessionTokenV1Store } from '../../../app/models'; +import { registry, User, dbAdapter, sessionTokenV1Store } from '../../../app/models'; import { withAuthToken } from '../../../app/controllers/middlewares/with-auth-token'; import { ACTIVE, CLOSED } from '../../../app/models/auth-tokens/SessionTokenV1'; import { fallbackIP, fallbackUserAgent } from '../../../app/models/common'; @@ -80,6 +80,7 @@ describe('withAuthToken middleware', () => { }, request: { body: {} }, state: { matchedRoute: '/v1/posts' }, + modelRegistry: registry, }); before(async () => { From e51662ada004d57db591f0dd9d7e8c493d6616da Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sun, 15 May 2022 01:50:38 +0300 Subject: [PATCH 08/15] Use proper type for Middleware --- app/controllers/middlewares/with-auth-token.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/middlewares/with-auth-token.ts b/app/controllers/middlewares/with-auth-token.ts index a88b7b452..514e20f97 100644 --- a/app/controllers/middlewares/with-auth-token.ts +++ b/app/controllers/middlewares/with-auth-token.ts @@ -2,8 +2,9 @@ import util from 'util'; import jwt from 'jsonwebtoken'; import config from 'config'; -import { Context, Next } from 'koa'; +import { Middleware, DefaultState } from 'koa'; +import { type FreefeedContext } from '../../freefeed-app'; import { NotAuthorizedException } from '../../support/exceptions'; import { authDebugError, AuthToken, SessionTokenV1, AppTokenV1 } from '../../models/auth-tokens'; import { sessionTokenV1Store } from '../../models'; @@ -15,7 +16,7 @@ declare module 'jsonwebtoken' { jwt.verifyAsync = util.promisify(jwt.verify); -export async function withAuthToken(ctx: Context, next: Next) { +export const withAuthToken: Middleware = async (ctx, next) => { let jwtToken; if (ctx.headers['authorization']) { @@ -66,4 +67,4 @@ export async function withAuthToken(ctx: Context, next: Next) { ctx.state = { ...ctx.state, authToken, authJWTPayload: payload }; await authToken.middleware(ctx, next); -} +}; From d2485ac915ecab46f05c3e63eacd4aa42cc2eb3c Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sun, 15 May 2022 15:27:10 +0300 Subject: [PATCH 09/15] Decouple AuthToken from global DbAdapter --- app/models/auth-tokens/AppTokenV1.ts | 6 +----- app/models/auth-tokens/AuthToken.ts | 10 +++++++--- app/models/auth-tokens/SessionTokenV1.ts | 5 +---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/app/models/auth-tokens/AppTokenV1.ts b/app/models/auth-tokens/AppTokenV1.ts index 88f0b2040..eb9491817 100644 --- a/app/models/auth-tokens/AppTokenV1.ts +++ b/app/models/auth-tokens/AppTokenV1.ts @@ -56,12 +56,8 @@ export class AppTokenV1 extends AuthToken { public lastUserAgent: Nullable; public activationCode: Nullable; - private readonly [database]: DbAdapter; - constructor(params: AppTokenRecord, dbAdapter: DbAdapter) { - super(params.userId); - - this[database] = dbAdapter; + super(params.userId, dbAdapter); this.id = params.id; this.createdAt = params.createdAt; diff --git a/app/models/auth-tokens/AuthToken.ts b/app/models/auth-tokens/AuthToken.ts index 45369ada6..64bd3190b 100644 --- a/app/models/auth-tokens/AuthToken.ts +++ b/app/models/auth-tokens/AuthToken.ts @@ -1,8 +1,9 @@ import { Context, Next } from 'koa'; -import { dbAdapter } from '../../models'; import { NotAuthorizedException } from '../../support/exceptions'; import { UUID } from '../../support/types'; +import { DbAdapter } from '../../support/DbAdapter'; +import { database } from '../common'; import { authDebug, authDebugError } from '.'; @@ -12,13 +13,16 @@ import { authDebug, authDebugError } from '.'; */ export abstract class AuthToken { readonly hasFullAccess: boolean = false; + readonly [database]: DbAdapter; - constructor(public readonly userId: UUID) {} + constructor(public readonly userId: UUID, dbAdapter: DbAdapter) { + this[database] = dbAdapter; + } abstract tokenString(): string; getUser() { - return dbAdapter.getUserById(this.userId); + return this[database].getUserById(this.userId); } async middleware(ctx: Context, next: Next) { diff --git a/app/models/auth-tokens/SessionTokenV1.ts b/app/models/auth-tokens/SessionTokenV1.ts index 3261a62b4..009d03367 100644 --- a/app/models/auth-tokens/SessionTokenV1.ts +++ b/app/models/auth-tokens/SessionTokenV1.ts @@ -49,11 +49,8 @@ export class SessionTokenV1 extends AuthToken { public lastUserAgent!: string; public databaseTime!: Date; - private readonly [database]: DbAdapter; - constructor(params: SessionRecord, dbAdapter: DbAdapter) { - super(params.userId); - this[database] = dbAdapter; + super(params.userId, dbAdapter); this.copyFieldsFrom(params); } From 32b2c8fed39c98448975165bfd370b425b4d691f Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sun, 15 May 2022 16:05:15 +0300 Subject: [PATCH 10/15] Decouple passport-init from global dbAdapter --- app/setup/environment.js | 7 +++++-- app/setup/initializers/passport.js | 4 +--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/setup/environment.js b/app/setup/environment.js index d7f3e56c9..5a2c42a5b 100644 --- a/app/setup/environment.js +++ b/app/setup/environment.js @@ -32,8 +32,6 @@ if (sentryIsEnabled) { const log = createDebug('freefeed:init'); process.env.MONITOR_PREFIX = config.monitorPrefix; -passportInit(passport); - const checkIfMediaDirectoriesExist = async () => { let gotErrors = false; @@ -79,6 +77,11 @@ export const init = async function () { await setPostgresSearchConfig(); + const { + default: { registry }, + } = await import('../models.js'); // FIXME: replace this with explicit initialization + passportInit(passport, registry.dbAdapter); + if (config.media.storage.type === 'fs') { await checkIfMediaDirectoriesExist(); } diff --git a/app/setup/initializers/passport.js b/app/setup/initializers/passport.js index bc7f4e306..11ae860cc 100644 --- a/app/setup/initializers/passport.js +++ b/app/setup/initializers/passport.js @@ -1,8 +1,6 @@ import { Strategy as LocalStrategy } from 'passport-local'; -import { dbAdapter } from '../../models'; - -export function init(passport) { +export function init(passport, dbAdapter) { passport.use( new LocalStrategy( { From 847522d4c950c4e291371656d1e98bf8620483d4 Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sun, 15 May 2022 16:47:38 +0300 Subject: [PATCH 11/15] Decouple BestOf, Notification digests from globals --- app/support/BestOfDigest.js | 11 ++++++----- app/support/NotificationsDigest.js | 3 +-- bin/bestof_emails.js | 3 ++- bin/notification_emails.js | 3 ++- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/support/BestOfDigest.js b/app/support/BestOfDigest.js index 079d28e9b..b8be4401c 100644 --- a/app/support/BestOfDigest.js +++ b/app/support/BestOfDigest.js @@ -2,14 +2,14 @@ import moment from 'moment'; import createDebug from 'debug'; import _ from 'lodash'; -import { dbAdapter } from '../models'; import { sendDailyBestOfEmail, sendWeeklyBestOfEmail } from '../mailers/BestOfDigestMailer'; import { generalSummary } from '../controllers/api/v2/SummaryController.js'; const BESTOF_DIGEST_POSTS_LIMIT = 15; -export async function sendBestOfEmails() { +export async function sendBestOfEmails(registry) { const debug = createDebug('freefeed:sendBestOfEmails'); + const { dbAdapter } = registry; const weeklyDigestRecipients = (await dbAdapter.getWeeklyBestOfDigestRecipients()).filter( (u) => u.isActive, @@ -41,7 +41,7 @@ export async function sendBestOfEmails() { } debug(`[${u.username}] -> getSummary()`); - const weeklySummary = await getSummary(u, 7); // eslint-disable-line no-await-in-loop + const weeklySummary = await getSummary(registry, u, 7); // eslint-disable-line no-await-in-loop if (!canMakeBestOfEmail(weeklySummary)) { debug(`[${u.username}] getSummary() returned 0 posts: SKIP`); @@ -72,7 +72,7 @@ export async function sendBestOfEmails() { } debug(`[${u.username}] -> getSummary()`); - const dailySummary = await getSummary(u, 1); // eslint-disable-line no-await-in-loop + const dailySummary = await getSummary(registry, u, 1); // eslint-disable-line no-await-in-loop if (!canMakeBestOfEmail(dailySummary)) { debug(`[${u.username}] getSummary() returned 0 posts: SKIP`); @@ -162,11 +162,12 @@ function preparePosts(payload, recipient) { return payload; } -async function getSummary(user, days) { +async function getSummary(registry, user, days) { const ctx = { request: { query: { limit: BESTOF_DIGEST_POSTS_LIMIT } }, state: { user }, params: { days }, + modelRegistry: registry, }; await generalSummary(ctx); diff --git a/app/support/NotificationsDigest.js b/app/support/NotificationsDigest.js index 30e5b344f..5632e1ced 100644 --- a/app/support/NotificationsDigest.js +++ b/app/support/NotificationsDigest.js @@ -1,13 +1,12 @@ import moment from 'moment'; import createDebug from 'debug'; -import { dbAdapter } from '../models'; import { serializeEvents } from '../serializers/v2/event'; import { sendEventsDigestEmail } from '../mailers/NotificationDigestMailer'; import { DIGEST_EVENT_TYPES } from './EventTypes'; -export async function sendEmails() { +export async function sendEmails(dbAdapter) { const debug = createDebug('freefeed:sendEmails'); const users = await dbAdapter.getNotificationsDigestRecipients(); diff --git a/bin/bestof_emails.js b/bin/bestof_emails.js index bd7d8d044..da3fe8292 100644 --- a/bin/bestof_emails.js +++ b/bin/bestof_emails.js @@ -1,7 +1,8 @@ #!/usr/bin/env babel-node import { sendBestOfEmails } from '../app/support/BestOfDigest'; +import { registry } from '../app/models'; -sendBestOfEmails() +sendBestOfEmails(registry) .then(() => { process.stdout.write('Finished\n'); process.exit(0); diff --git a/bin/notification_emails.js b/bin/notification_emails.js index 2c8c40d62..bd9a85265 100755 --- a/bin/notification_emails.js +++ b/bin/notification_emails.js @@ -1,7 +1,8 @@ #!/usr/bin/env babel-node import { sendEmails } from '../app/support/NotificationsDigest'; +import { registry } from '../app/models'; -sendEmails() +sendEmails(registry.dbAdapter) .then(() => { process.stdout.write('Finished\n'); process.exit(0); From 31eb63ac4d9cf819117ab0c6b45f6b60b70c83c1 Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sun, 15 May 2022 17:38:32 +0300 Subject: [PATCH 12/15] Decouple jobs from global DbManager --- .../api/v1/AttachmentsController.js | 2 +- app/jobs/attachments-sanitize.ts | 13 +- app/jobs/periodic/auth-tokens.ts | 6 +- app/jobs/user-gone.js | 22 +-- app/models.d.ts | 4 + app/models/job.js | 3 + app/support/user-deletion.ts | 134 +++++++++--------- test/integration/support/user-deletion.js | 30 ++-- 8 files changed, 114 insertions(+), 100 deletions(-) diff --git a/app/controllers/api/v1/AttachmentsController.js b/app/controllers/api/v1/AttachmentsController.js index e205b2f78..58c6cd574 100644 --- a/app/controllers/api/v1/AttachmentsController.js +++ b/app/controllers/api/v1/AttachmentsController.js @@ -128,7 +128,7 @@ export default class AttachmentsController { authRequired(), async (ctx) => { const { user } = ctx.state; - const task = await startAttachmentsSanitizeJob(user); + const task = await startAttachmentsSanitizeJob(ctx.modelRegistry.dbAdapter, user); ctx.body = { sanitizeTask: { createdAt: task.createdAt }, }; diff --git a/app/jobs/attachments-sanitize.ts b/app/jobs/attachments-sanitize.ts index 1de033b73..557cd8589 100644 --- a/app/jobs/attachments-sanitize.ts +++ b/app/jobs/attachments-sanitize.ts @@ -2,14 +2,15 @@ import createDebug from 'debug'; import { DateTime } from 'luxon'; import Raven from 'raven'; -import { dbAdapter, Job, JobManager, type User } from '../models'; +import { Job, JobManager, type User } from '../models'; import { UUID } from '../support/types'; +import { DbAdapter } from '../support/DbAdapter'; const debug = createDebug('freefeed:model:attachment'); export const ATTACHMENTS_SANITIZE = 'ATTACHMENTS_SANITIZE'; -export async function startAttachmentsSanitizeJob(user: User) { +export async function startAttachmentsSanitizeJob(dbAdapter: DbAdapter, user: User) { await Job.create(ATTACHMENTS_SANITIZE, { userId: user.id }, { uniqKey: user.id }); return dbAdapter.createAttachmentsSanitizeTask(user.id); } @@ -23,7 +24,7 @@ export function initHandlers(jobManager: JobManager) { // Check if this job is stuck { - const { total: totalAttachments } = await dbAdapter.getAttachmentsStats(userId); + const { total: totalAttachments } = await jobManager.dbAdapter.getAttachmentsStats(userId); const estJobRuns = totalAttachments / batchSize; if (job.attempts > estJobRuns * 2) { @@ -36,7 +37,7 @@ export function initHandlers(jobManager: JobManager) { ), ); - await dbAdapter.deleteAttachmentsSanitizeTask(userId); + await jobManager.dbAdapter.deleteAttachmentsSanitizeTask(userId); // Don't keep it return; @@ -46,11 +47,11 @@ export function initHandlers(jobManager: JobManager) { await job.setUnlockAt(maxTTL * 1.5); const workUntil = DateTime.local().plus({ seconds: maxTTL }).toJSDate(); - const attachments = await dbAdapter.getNonSanitizedAttachments(userId, batchSize); + const attachments = await jobManager.dbAdapter.getNonSanitizedAttachments(userId, batchSize); if (attachments.length === 0) { // Nothing to do - await dbAdapter.deleteAttachmentsSanitizeTask(userId); + await jobManager.dbAdapter.deleteAttachmentsSanitizeTask(userId); return; } diff --git a/app/jobs/periodic/auth-tokens.ts b/app/jobs/periodic/auth-tokens.ts index 4df088957..af3b76cd9 100644 --- a/app/jobs/periodic/auth-tokens.ts +++ b/app/jobs/periodic/auth-tokens.ts @@ -1,6 +1,6 @@ import config from 'config'; -import { dbAdapter, JobManager } from '../../models'; +import { JobManager } from '../../models'; import FreefeedApp from '../../freefeed-app'; import { definePeriodicJob } from '.'; @@ -13,14 +13,14 @@ export function initHandlers(jobManager: JobManager, app: FreefeedApp) { return Promise.all([ definePeriodicJob(jobManager, { name: PERIODIC_INACTIVATE_APP_TOKENS, - handler: () => dbAdapter.periodicInvalidateAppTokens(), + handler: () => jobManager.dbAdapter.periodicInvalidateAppTokens(), nextTime: 10 * 60, // every 10 minutes payload: {}, }), definePeriodicJob(jobManager, { name: PERIODIC_CLEAN_AUTH_SESSIONS, handler: () => - dbAdapter.cleanOldAuthSessions( + jobManager.dbAdapter.cleanOldAuthSessions( config.authSessions.activeSessionTTLDays, config.authSessions.inactiveSessionTTLDays, ), diff --git a/app/jobs/user-gone.js b/app/jobs/user-gone.js index 432534d4c..4d83aa79f 100644 --- a/app/jobs/user-gone.js +++ b/app/jobs/user-gone.js @@ -1,10 +1,10 @@ import config from 'config'; import { DateTime } from 'luxon'; -import { Job, dbAdapter } from '../models'; +import { Job } from '../models'; import { GONE_COOLDOWN, GONE_DELETION, GONE_DELETED } from '../models/user'; import Mailer from '../../lib/mailer'; -import { deleteAllUserData } from '../../app/support/user-deletion'; +import { deleteAllUserData } from '../support/user-deletion'; // User deletion process export const USER_COOLDOWN_START = 'USER_COOLDOWN_START'; @@ -40,7 +40,7 @@ export function userDataDeletionStart(user) { export function initHandlers(jobManager) { jobManager.on( USER_COOLDOWN_START, - checkUserStatus(GONE_COOLDOWN, async (user) => { + checkUserStatus(jobManager.dbAdapter, GONE_COOLDOWN, async (user) => { // Send email to user await Mailer.sendMail( // User is gone so the regular .email field is empty @@ -68,7 +68,7 @@ export function initHandlers(jobManager) { jobManager.on( USER_COOLDOWN_REMINDER, - checkUserStatus(GONE_COOLDOWN, async (user) => { + checkUserStatus(jobManager.dbAdapter, GONE_COOLDOWN, async (user) => { // Send email to user await Mailer.sendMail( // User is gone so the regular .email field is empty @@ -82,7 +82,9 @@ export function initHandlers(jobManager) { jobManager.on( USER_DELETION_START, - checkUserStatus(GONE_COOLDOWN, (user) => user.setGoneStatus(GONE_DELETION)), + checkUserStatus(jobManager.dbAdapter, GONE_COOLDOWN, (user) => + user.setGoneStatus(GONE_DELETION), + ), ); jobManager.on(USER_DELETE_DATA, async (job) => { @@ -91,9 +93,13 @@ export function initHandlers(jobManager) { const { id, email } = job.payload; - await deleteAllUserData(id, DateTime.local().plus({ seconds: maxTTL }).toJSDate()); + await deleteAllUserData( + jobManager.dbAdapter, + id, + DateTime.local().plus({ seconds: maxTTL }).toJSDate(), + ); - const user = await dbAdapter.getUserById(id); + const user = await jobManager.dbAdapter.getUserById(id); if (user.goneStatus === GONE_DELETED) { // All data has been deleted, send email and finish job @@ -115,7 +121,7 @@ export function initHandlers(jobManager) { // Helpers -const checkUserStatus = (desiredStatus, handler) => async (job) => { +const checkUserStatus = (dbAdapter, desiredStatus, handler) => async (job) => { const { id, goneAt } = job.payload; const user = await dbAdapter.getUserById(id); diff --git a/app/models.d.ts b/app/models.d.ts index d1427df5e..95f35e151 100644 --- a/app/models.d.ts +++ b/app/models.d.ts @@ -1,4 +1,5 @@ import Knex from 'knex'; +import type Config from 'config'; import { DbAdapter } from './support/DbAdapter'; import PubSubAdapter from './pubsub'; @@ -152,6 +153,9 @@ export type JobHandler

= (job: Job

) => Promise; export type JobMiddleware = (h: JobHandler) => JobHandler; export class JobManager { + readonly dbAdapter: DbAdapter; + + constructor(props: Partial); on

(name: string, handler: JobHandler

): () => void; fetchAndProcess(): Promise; use(mw: JobMiddleware): void; diff --git a/app/models/job.js b/app/models/job.js index 787f96333..d9f221811 100644 --- a/app/models/job.js +++ b/app/models/job.js @@ -90,6 +90,8 @@ export function addJobManagerModel(dbAdapter) { _pollTimer = null; _handlers = new Map(); + dbAdapter; + /** * @param {Partial} [props] */ @@ -100,6 +102,7 @@ export function addJobManagerModel(dbAdapter) { this.maxJobLockTime = props.maxJobLockTime; this.jobLockTimeMultiplier = props.jobLockTimeMultiplier; this.batchSize = props.batchSize; + this.dbAdapter = dbAdapter; } /** diff --git a/app/support/user-deletion.ts b/app/support/user-deletion.ts index e1696b4da..e152e95c5 100644 --- a/app/support/user-deletion.ts +++ b/app/support/user-deletion.ts @@ -1,8 +1,10 @@ -import { dbAdapter } from '../models'; import { GONE_DELETED } from '../models/user'; import { forEachAsync } from './forEachAsync'; import { UUID } from './types'; +import { type DbAdapter } from './DbAdapter'; + +type Task = (dbAdapter: DbAdapter, userId: UUID, runUntil: Date) => Promise; // Objects to delete: // 1. [x] User personal information @@ -24,38 +26,12 @@ import { UUID } from './types'; // 17. [x] Statistics (posts, likes, subscriptions = 0, update comments count) // 18. [x] User's attachments -/** - * Delete user data. User must be in GONE_DELETION status, when all data is - * deleted the status becomes GONE_DELETED. - */ -export const deleteAllUserData = combineTasks( - deletePersonalInfo, - deletePosts, - deleteLikes, - deleteCommentLikes, - unbanAll, - deleteSubscriptions, - deleteSubscriptionRequests, - deleteAuxHomeFeeds, - deleteNotifications, - deleteAppTokens, - deleteExtAuthProfiles, - deleteArchives, - deleteInvitations, - deleteLocalBumps, - deleteSentEmailsLog, - resetUserStatistics, - deleteAttachments, - // This ↓ must be the last task - setDeletedStatus, -); - -async function setDeletedStatus(userId: UUID) { +const setDeletedStatus: Task = async (dbAdapter, userId) => { const user = await dbAdapter.getUserById(userId); await user?.setGoneStatus(GONE_DELETED); -} +}; -export async function deletePersonalInfo(userId: UUID) { +export const deletePersonalInfo: Task = async (dbAdapter, userId) => { const userRow = await dbAdapter.database.getRow(`select * from users where uid = ?`, userId); // Update all 'users' row fields to their default values except for some fields @@ -80,9 +56,9 @@ export async function deletePersonalInfo(userId: UUID) { ); await dbAdapter.database('users').where('uid', userId).update(payload); -} +}; -export async function deletePosts(userId: UUID, runUntil: Date) { +export const deletePosts: Task = async (dbAdapter, userId, runUntil) => { const batchSize = 20; do { @@ -102,9 +78,9 @@ export async function deletePosts(userId: UUID, runUntil: Date) { await post?.destroy(); }); } while (new Date() < runUntil); -} +}; -export async function deleteLikes(userId: UUID, runUntil: Date) { +export const deleteLikes: Task = async (dbAdapter, userId, runUntil) => { const batchSize = 50; const user = await dbAdapter.getUserById(userId); @@ -129,9 +105,9 @@ export async function deleteLikes(userId: UUID, runUntil: Date) { await post?.removeLike(user); }); } while (new Date() < runUntil); -} +}; -export async function deleteCommentLikes(userId: UUID, runUntil: Date) { +export const deleteCommentLikes: Task = async (dbAdapter, userId, runUntil) => { const batchSize = 50; const user = await dbAdapter.getUserById(userId); @@ -160,9 +136,9 @@ export async function deleteCommentLikes(userId: UUID, runUntil: Date) { await comment?.removeLike(user); }); } while (new Date() < runUntil); -} +}; -export async function unbanAll(userId: UUID) { +export const unbanAll: Task = async (dbAdapter, userId) => { const user = await dbAdapter.getUserById(userId); if (!user) { @@ -181,9 +157,9 @@ export async function unbanAll(userId: UUID) { ); await forEachAsync(usernames, (username: string) => user?.unban(username)); -} +}; -export async function deleteSubscriptions(userId: UUID, runUntil: Date) { +export const deleteSubscriptions: Task = async (dbAdapter, userId, runUntil) => { const user = await dbAdapter.getUserById(userId); if (!user) { @@ -199,26 +175,26 @@ export async function deleteSubscriptions(userId: UUID, runUntil: Date) { friend && (await user.unsubscribeFrom(friend)); } }); -} +}; -export async function deleteSubscriptionRequests(userId: UUID) { +export const deleteSubscriptionRequests: Task = async (dbAdapter, userId) => { const toUserIds = await dbAdapter.getUserSubscriptionPendingRequestsIds(userId); await forEachAsync(toUserIds, (toUserId: UUID) => dbAdapter.deleteSubscriptionRequest(toUserId, userId), ); -} +}; -export async function deleteAuxHomeFeeds(userId: UUID) { +export const deleteAuxHomeFeeds: Task = async (dbAdapter, userId) => { const user = await dbAdapter.getUserById(userId); if (user) { const homeFeeds = await user.getHomeFeeds(); await forEachAsync(homeFeeds, (homeFeed) => homeFeed.destroy()); } -} +}; -export async function deleteNotifications(userId: UUID) { +export const deleteNotifications: Task = async (dbAdapter, userId) => { const user = await dbAdapter.getUserById(userId); // Remove user's notifications caused by the user themself @@ -227,9 +203,9 @@ export async function deleteNotifications(userId: UUID) { `delete from events where user_id = ? and created_by_user_id = user_id`, user.intId, )); -} +}; -export async function deleteAppTokens(userId: UUID, runUntil: Date) { +export const deleteAppTokens: Task = async (dbAdapter, userId, runUntil) => { const batchSize = 50; do { @@ -249,35 +225,35 @@ export async function deleteAppTokens(userId: UUID, runUntil: Date) { await token?.destroy(); }); } while (new Date() < runUntil); -} +}; -export async function deleteExtAuthProfiles(userId: UUID) { +export const deleteExtAuthProfiles: Task = async (dbAdapter, userId) => { const profiles = await dbAdapter.getExtProfiles(userId); await Promise.all(profiles.map((p: { id: UUID }) => dbAdapter.removeExtProfile(userId, p.id))); -} +}; -export async function deleteArchives(userId: UUID) { +export const deleteArchives: Task = async (dbAdapter, userId) => { await dbAdapter.database.raw(`delete from archives where user_id = ?`, userId); await dbAdapter.database.raw(`delete from hidden_comments where user_id = ?`, userId); await dbAdapter.database.raw(`delete from hidden_likes where user_id = ?`, userId); -} +}; -export async function deleteInvitations(userId: UUID) { +export const deleteInvitations: Task = async (dbAdapter, userId) => { const user = await dbAdapter.getUserById(userId); user && (await dbAdapter.database.raw(`delete from invitations where author = ?`, user.intId)); -} +}; -export async function deleteLocalBumps(userId: UUID) { +export const deleteLocalBumps: Task = async (dbAdapter, userId) => { await dbAdapter.database.raw(`delete from local_bumps where user_id = ?`, userId); -} +}; -export async function deleteSentEmailsLog(userId: UUID) { +export const deleteSentEmailsLog: Task = async (dbAdapter, userId) => { const user = await dbAdapter.getUserById(userId); user && (await dbAdapter.database.raw(`delete from sent_emails_log where user_id = ?`, user.intId)); -} +}; -export async function resetUserStatistics(userId: UUID) { +export const resetUserStatistics: Task = async (dbAdapter, userId) => { // We still have an unattached comments problem so we need to count only // comments of the real posts. const commentsCount = await dbAdapter.database.getOne( @@ -296,9 +272,9 @@ export async function resetUserStatistics(userId: UUID) { where user_id = :userId`, { userId, commentsCount }, ); -} +}; -export async function deleteAttachments(userId: UUID, runUntil: Date) { +export const deleteAttachments: Task = async (dbAdapter, userId, runUntil) => { const batchSize = 20; do { @@ -318,17 +294,41 @@ export async function deleteAttachments(userId: UUID, runUntil: Date) { await att?.destroy(); }); } while (new Date() < runUntil); -} +}; -// Helpers +/** + * Delete user data. User must be in GONE_DELETION status, when all data is + * deleted the status becomes GONE_DELETED. + */ +export const deleteAllUserData: Task = combineTasks( + deletePersonalInfo, + deletePosts, + deleteLikes, + deleteCommentLikes, + unbanAll, + deleteSubscriptions, + deleteSubscriptionRequests, + deleteAuxHomeFeeds, + deleteNotifications, + deleteAppTokens, + deleteExtAuthProfiles, + deleteArchives, + deleteInvitations, + deleteLocalBumps, + deleteSentEmailsLog, + resetUserStatistics, + deleteAttachments, + // This ↓ must be the last task + setDeletedStatus, +); -type Task = (userId: UUID, runUntil: Date) => Promise; +// Helpers function combineTasks(...tasks: Task[]): Task { - return (userId: UUID, runUntil: Date) => + return (dbAdapter, userId, runUntil) => forEachAsync(tasks, async (task: Task) => { if (new Date() < runUntil) { - await task(userId, runUntil); + await task(dbAdapter, userId, runUntil); } }); } diff --git a/test/integration/support/user-deletion.js b/test/integration/support/user-deletion.js index 6d09f08e4..5ec563912 100644 --- a/test/integration/support/user-deletion.js +++ b/test/integration/support/user-deletion.js @@ -46,12 +46,12 @@ describe('User data deletion', () => { }); it(`should pass smoke test`, () => - expect(deleteAllUserData(luna.id, afterHour()), 'to be fulfilled')); + expect(deleteAllUserData(dbAdapter, luna.id, afterHour()), 'to be fulfilled')); it(`should delete user personal data`, async () => { await luna.setGoneStatus(GONE_DELETION); - await deletePersonalInfo(luna.id); + await deletePersonalInfo(dbAdapter, luna.id); const newData = await dbAdapter.database.getRow(`select * from users where uid = ?`, luna.id); expect(newData, 'to satisfy', { @@ -77,7 +77,7 @@ describe('User data deletion', () => { await post.create(); } - await deletePosts(luna.id, afterHour()); + await deletePosts(dbAdapter, luna.id, afterHour()); const postIds = await dbAdapter.database.getCol( `select uid from posts where user_id = ?`, luna.id, @@ -103,7 +103,7 @@ describe('User data deletion', () => { expect(likes, 'to satisfy', [{ user_id: luna.id, post_id: marsPost.id }]); } - await deleteLikes(luna.id, afterHour()); + await deleteLikes(dbAdapter, luna.id, afterHour()); { const likes = await dbAdapter.database.getAll( @@ -139,7 +139,7 @@ describe('User data deletion', () => { expect(likes, 'to have length', 1); } - await deleteCommentLikes(luna.id, afterHour()); + await deleteCommentLikes(dbAdapter, luna.id, afterHour()); { const likes = await dbAdapter.database.getAll( @@ -153,7 +153,7 @@ describe('User data deletion', () => { it(`should delete user bans`, async () => { await luna.ban(mars.username); - await unbanAll(luna.id, afterHour()); + await unbanAll(dbAdapter, luna.id, afterHour()); { const bans = await dbAdapter.database.getAll(`select * from bans where user_id = ?`, luna.id); @@ -164,7 +164,7 @@ describe('User data deletion', () => { it(`should delete user subscriptions`, async () => { await luna.subscribeTo(mars); - await deleteSubscriptions(luna.id, afterHour()); + await deleteSubscriptions(dbAdapter, luna.id, afterHour()); { const friends = await luna.getSubscriptionsWithHomeFeeds(); @@ -185,7 +185,7 @@ describe('User data deletion', () => { expect(requests, 'to have length', 2); } - await deleteSubscriptionRequests(luna.id, afterHour()); + await deleteSubscriptionRequests(dbAdapter, luna.id, afterHour()); { const requests = await luna.getPendingSubscriptionRequestIds(); @@ -202,7 +202,7 @@ describe('User data deletion', () => { expect(feeds, 'to have length', 3); } - await deleteAuxHomeFeeds(luna.id); + await deleteAuxHomeFeeds(dbAdapter, luna.id); { const feeds = await luna.getHomeFeeds(); @@ -219,7 +219,7 @@ describe('User data deletion', () => { expect(notifications, 'to have length', 2); } - await deleteNotifications(luna.id); + await deleteNotifications(dbAdapter, luna.id); { const notifications = await dbAdapter.database.getAll(`select * from events`); @@ -239,7 +239,7 @@ describe('User data deletion', () => { expect(tokenIds, 'to have length', 2); } - await deleteAppTokens(luna.id, afterHour()); + await deleteAppTokens(dbAdapter, luna.id, afterHour()); { const tokenIds = await dbAdapter.database.getAll( @@ -267,7 +267,7 @@ describe('User data deletion', () => { expect(profiles, 'to have length', 2); } - await deleteExtAuthProfiles(luna.id); + await deleteExtAuthProfiles(dbAdapter, luna.id); { const profiles = await dbAdapter.database.getCol(`select uid from external_auth`); @@ -283,7 +283,7 @@ describe('User data deletion', () => { expect(await dbAdapter.getUserArchiveParams(luna.id), 'not to be null'); - await deleteArchives(luna.id); + await deleteArchives(dbAdapter, luna.id); expect(await dbAdapter.getUserArchiveParams(luna.id), 'to be null'); }); @@ -300,7 +300,7 @@ describe('User data deletion', () => { expect(await dbAdapter.database.getOne(`select count(*)::int from invitations`), 'to be', 1); - await deleteInvitations(luna.id); + await deleteInvitations(dbAdapter, luna.id); expect(await dbAdapter.database.getOne(`select count(*)::int from invitations`), 'to be', 0); }); @@ -324,7 +324,7 @@ describe('User data deletion', () => { await att.create(); await filesMustExist(att); - await deleteAttachments(luna.id, afterHour()); + await deleteAttachments(dbAdapter, luna.id, afterHour()); expect(await dbAdapter.database.getOne(`select count(*)::int from attachments`), 'to be', 0); await filesMustExist(att, false); From f8de85e774903173fc39dc1274c260722d23715a Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sun, 15 May 2022 18:42:16 +0300 Subject: [PATCH 13/15] Decouple EventService from globals --- app/controllers/api/v1/GroupsController.js | 21 +- app/controllers/api/v1/UsersController.js | 35 +- app/controllers/api/v2/RequestsController.js | 8 +- app/models-registry.ts | 10 +- app/models/comment.js | 11 +- app/models/post.js | 16 +- app/models/user-prefs.ts | 4 +- app/models/user.js | 25 +- app/support/EventService.ts | 484 ++++++++++--------- 9 files changed, 339 insertions(+), 275 deletions(-) diff --git a/app/controllers/api/v1/GroupsController.js b/app/controllers/api/v1/GroupsController.js index 99ca0214f..0fd080fa2 100644 --- a/app/controllers/api/v1/GroupsController.js +++ b/app/controllers/api/v1/GroupsController.js @@ -2,7 +2,6 @@ import _ from 'lodash'; import compose from 'koa-compose'; import { AppTokenV1 } from '../../../models/auth-tokens/AppTokenV1'; -import { EventService } from '../../../support/EventService'; import { BadRequestException, NotFoundException, @@ -38,7 +37,7 @@ export default class GroupsController { const group = new ctx.modelRegistry.Group(params); await group.create(ctx.state.user.id, false); - await EventService.onGroupCreated(ctx.state.user.intId, group.intId); + await ctx.modelRegistry.eventService.onGroupCreated(ctx.state.user.intId, group.intId); // The same output as of the UsersController.show with 'users' -> 'groups' replacing ctx.params['username'] = group.username; @@ -158,10 +157,18 @@ export default class GroupsController { if (newStatus) { await group.addAdministrator(newAdmin.id); - await EventService.onGroupAdminPromoted(ctx.state.user.intId, group, newAdmin.intId); + await ctx.modelRegistry.eventService.onGroupAdminPromoted( + ctx.state.user.intId, + group, + newAdmin.intId, + ); } else { await group.removeAdministrator(newAdmin.id); - await EventService.onGroupAdminDemoted(ctx.state.user.intId, group, newAdmin.intId); + await ctx.modelRegistry.eventService.onGroupAdminDemoted( + ctx.state.user.intId, + group, + newAdmin.intId, + ); } ctx.body = { err: null, status: 'success' }; @@ -279,7 +286,11 @@ export default class GroupsController { } await group.rejectSubscriptionRequest(user.id); - await EventService.onGroupSubscriptionRequestRejected(ctx.state.user.intId, group, user.intId); + await ctx.modelRegistry.eventService.onGroupSubscriptionRequestRejected( + ctx.state.user.intId, + group, + user.intId, + ); ctx.body = { err: null, status: 'success' }; } diff --git a/app/controllers/api/v1/UsersController.js b/app/controllers/api/v1/UsersController.js index 294e1bd0f..c26fc21cf 100644 --- a/app/controllers/api/v1/UsersController.js +++ b/app/controllers/api/v1/UsersController.js @@ -16,7 +16,6 @@ import { BadRequestException, TooManyRequestsException, } from '../../../support/exceptions'; -import { EventService } from '../../../support/EventService'; import recaptchaVerify from '../../../../lib/recaptcha'; import { serializeUsersByIds } from '../../../serializers/v2/user'; import { @@ -116,7 +115,7 @@ export default class UsersController { user, invitation, ctx.request.body.cancel_subscription, - ctx.modelRegistry.dbAdapter, + ctx.modelRegistry, ), ), // Subscribe to onboarding user @@ -240,9 +239,15 @@ export default class UsersController { await user.sendSubscriptionRequest(targetUser.id, ctx.request.body.homeFeeds); if (targetUser.isUser()) { - await EventService.onSubscriptionRequestCreated(user.intId, targetUser.intId); + await ctx.modelRegistry.eventService.onSubscriptionRequestCreated( + user.intId, + targetUser.intId, + ); } else { - await EventService.onGroupSubscriptionRequestCreated(user.intId, targetUser); + await ctx.modelRegistry.eventService.onGroupSubscriptionRequestCreated( + user.intId, + targetUser, + ); } ctx.body = {}; @@ -288,7 +293,10 @@ export default class UsersController { } await ctx.state.user.rejectSubscriptionRequest(user.id); - await EventService.onSubscriptionRequestRejected(user.intId, ctx.state.user.intId); + await ctx.modelRegistry.eventService.onSubscriptionRequestRejected( + user.intId, + ctx.state.user.intId, + ); ctx.body = {}; } @@ -742,9 +750,18 @@ async function validateInvitationAndSelectUsers(invitation, invitationId, regist return { ...invitation, publicUsers, privateUsers, publicGroups, privateGroups }; } -async function useInvitation(newUser, invitation, cancel_subscription = false, dbAdapter) { +/** + * + * @param {User} newUser + * @param invitation + * @param {boolean} cancel_subscription + * @param {ModelsRegistry} registry + * @returns {Promise} + */ +async function useInvitation(newUser, invitation, cancel_subscription = false, registry) { + const { dbAdapter, eventService } = registry; await dbAdapter.useInvitation(invitation.secure_id); - await EventService.onInvitationUsed(invitation.author, newUser.intId); + await eventService.onInvitationUsed(invitation.author, newUser.intId); if (cancel_subscription) { return; @@ -765,14 +782,14 @@ async function useInvitation(newUser, invitation, cancel_subscription = false, d await Promise.all( invitation.privateUsers.map(async (recommendedUser) => { await newUser.sendSubscriptionRequest(recommendedUser.id); - return EventService.onSubscriptionRequestCreated(newUser.intId, recommendedUser.intId); + return eventService.onSubscriptionRequestCreated(newUser.intId, recommendedUser.intId); }), ); await Promise.all( invitation.privateGroups.map(async (recommendedGroup) => { await newUser.sendSubscriptionRequest(recommendedGroup.id); - return EventService.onGroupSubscriptionRequestCreated(newUser.intId, recommendedGroup); + return eventService.onGroupSubscriptionRequestCreated(newUser.intId, recommendedGroup); }), ); } diff --git a/app/controllers/api/v2/RequestsController.js b/app/controllers/api/v2/RequestsController.js index 9f55ff576..830c5b740 100644 --- a/app/controllers/api/v2/RequestsController.js +++ b/app/controllers/api/v2/RequestsController.js @@ -1,4 +1,3 @@ -import { EventService } from '../../../support/EventService'; import { NotFoundException } from '../../../support/exceptions'; export default class RequestsController { @@ -32,12 +31,15 @@ export default class RequestsController { await followedFeedOwner.rejectSubscriptionRequest(ctx.state.user.id); if (followedFeedOwner.type === 'user') { - await EventService.onSubscriptionRequestRevoked( + await ctx.modelRegistry.eventService.onSubscriptionRequestRevoked( ctx.state.user.intId, followedFeedOwner.intId, ); } else { - await EventService.onGroupSubscriptionRequestRevoked(ctx.state.user.intId, followedFeedOwner); + await ctx.modelRegistry.eventService.onGroupSubscriptionRequestRevoked( + ctx.state.user.intId, + followedFeedOwner, + ); } ctx.body = { err: null, status: 'success' }; diff --git a/app/models-registry.ts b/app/models-registry.ts index 6d4a34163..7e7ef9fb7 100644 --- a/app/models-registry.ts +++ b/app/models-registry.ts @@ -21,10 +21,12 @@ import { type Job, type JobManager, } from './models'; +import { EventService } from './support/EventService'; export class ModelsRegistry { readonly dbAdapter: DbAdapter; readonly pubSub: PubSub; + readonly eventService: EventService; readonly User: typeof User; readonly Group: typeof Group; @@ -40,14 +42,16 @@ export class ModelsRegistry { this.dbAdapter = new DbAdapter(database, this); this.pubSub = pubSub; - this.User = userModel(this, this.dbAdapter, this.pubSub); + this.User = userModel(this); this.Group = groupModel(this, this.dbAdapter, this.pubSub); - this.Post = postModel(this.dbAdapter, this.pubSub); + this.Post = postModel(this); this.Timeline = timelineModel(this.dbAdapter); this.Attachment = attachmentModel(this.dbAdapter); - this.Comment = commentModel(this.dbAdapter, this.pubSub); + this.Comment = commentModel(this); this.ServerInfo = addServerInfoModel(this.dbAdapter); this.Job = addJobModel(this.dbAdapter); this.JobManager = addJobManagerModel(this.dbAdapter); + + this.eventService = new EventService(this); } } diff --git a/app/models/comment.js b/app/models/comment.js index c81b57ce3..9ff664ed3 100644 --- a/app/models/comment.js +++ b/app/models/comment.js @@ -5,7 +5,6 @@ import monitor from 'monitor-dog'; import config from 'config'; import { extractHashtags } from '../support/hashtags'; -import { EventService } from '../support/EventService'; import { getRoomsOfPost } from '../pubsub-listener'; import { getUpdatedUUIDs, notifyBacklinkedLater, notifyBacklinkedNow } from '../support/backlinks'; import { List } from '../support/open-lists'; @@ -13,7 +12,9 @@ import { List } from '../support/open-lists'; /** * @returns {typeof import('../models').Comment} */ -export function addModel(dbAdapter, pubSub) { +export function addModel(registry) { + const { dbAdapter, pubSub } = registry; + class Comment { static VISIBLE = 0; static DELETED = 1; @@ -134,7 +135,7 @@ export function addModel(dbAdapter, pubSub) { this.processHashtagsOnCreate(), dbAdapter.statsCommentCreated(this.userId), pubSub.newComment(this), - EventService.onCommentChanged(this, true), + registry.eventService.onCommentChanged(this, true), notifyBacklinkedNow(this, pubSub, getUpdatedUUIDs(this.body)), ]); @@ -164,7 +165,7 @@ export function addModel(dbAdapter, pubSub) { await Promise.all([ this.processHashtagsOnUpdate(), pubSub.updateComment(this.id), - EventService.onCommentChanged(this), + registry.eventService.onCommentChanged(this), notifyBacklinked(), ]); @@ -216,7 +217,7 @@ export function addModel(dbAdapter, pubSub) { await Promise.all([ pubSub.destroyComment(this.id, this.postId, realtimeRooms), this.userId ? dbAdapter.statsCommentDeleted(this.userId) : null, - destroyedBy ? EventService.onCommentDestroyed(this, destroyedBy) : null, + destroyedBy ? registry.eventService.onCommentDestroyed(this, destroyedBy) : null, notifyBacklinked(), ]); diff --git a/app/models/post.js b/app/models/post.js index 4e77bf7d0..8b8405c6a 100644 --- a/app/models/post.js +++ b/app/models/post.js @@ -5,7 +5,6 @@ import config from 'config'; import { extractHashtags } from '../support/hashtags'; import { getRoomsOfPost } from '../pubsub-listener'; -import { EventService } from '../support/EventService'; import { List } from '../support/open-lists'; import { getUpdatedUUIDs, notifyBacklinkedLater, notifyBacklinkedNow } from '../support/backlinks'; @@ -22,10 +21,11 @@ import { */ /** - * @param {DbAdapter} dbAdapter + * @param {ModelsRegistry} registry * @returns {typeof import('../models').Post} */ -export function addModel(dbAdapter, pubSub) { +export function addModel(registry) { + const { dbAdapter, pubSub } = registry; class Post { id; attachments; @@ -153,7 +153,7 @@ export function addModel(dbAdapter, pubSub) { .map((f) => pubSub.updateUnreadDirects(f.userId)); rtUpdates.push(pubSub.newPost(this.id)); - await EventService.onPostCreated( + await registry.eventService.onPostCreated( newPost, destFeeds.map((f) => f.id), author, @@ -242,7 +242,7 @@ export function addModel(dbAdapter, pubSub) { dbAdapter.getTimelinesByIntIds(addedFeedIds), ]); afterUpdate.push(() => - EventService.onPostFeedsChanged(this, params.updatedBy || postAuthor, { + registry.eventService.onPostFeedsChanged(this, params.updatedBy || postAuthor, { addedFeeds, removedFeeds, }), @@ -258,7 +258,7 @@ export function addModel(dbAdapter, pubSub) { } afterUpdate.push(async () => { - await EventService.onPostCreated( + await registry.eventService.onPostCreated( this, await dbAdapter.getTimelinesUUIDsByIntIds(this.destinationFeedIds), await this.getCreatedBy(), @@ -315,7 +315,7 @@ export function addModel(dbAdapter, pubSub) { await Promise.all([ pubSub.destroyPost(this.id, realtimeRooms), - destroyedBy ? EventService.onPostDestroyed(this, destroyedBy, { groups }) : null, + destroyedBy ? registry.eventService.onPostDestroyed(this, destroyedBy, { groups }) : null, notifyBacklinked(), ]); } @@ -970,7 +970,7 @@ export function addModel(dbAdapter, pubSub) { return false; } - await EventService.onDirectLeft(this.id, user); + await registry.eventService.onDirectLeft(this.id, user); await pubSub.updatePost(this.id, { rooms, usersBeforeIds }); diff --git a/app/models/user-prefs.ts b/app/models/user-prefs.ts index e8357788c..fad39911d 100644 --- a/app/models/user-prefs.ts +++ b/app/models/user-prefs.ts @@ -10,8 +10,8 @@ import { addModel as userModelMaker } from './user'; const { defaults, overrides } = config.userPreferences; type UserPrefs = typeof defaults; -const commentModel = commentModelMaker(null); -const userModel = userModelMaker(null); +const commentModel = commentModelMaker({ dbAdapter: null, pubSub: null }); +const userModel = userModelMaker({ dbAdapter: null, pubSub: null }); const schema = { $schema: 'http://json-schema.org/schema#', diff --git a/app/models/user.js b/app/models/user.js index 8dff6350d..52f2c93a9 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -13,7 +13,6 @@ import config from 'config'; import { getS3 } from '../support/s3'; import { BadRequestException, NotFoundException, ValidationException } from '../support/exceptions'; -import { EventService } from '../support/EventService'; import { userCooldownStart, userDataDeletionStart } from '../jobs/user-gone'; import { allExternalProviders } from '../support/ExtAuth'; @@ -40,7 +39,9 @@ export const GONE_NAMES = { /** * @returns {typeof import('../models').User} */ -export function addModel(registry, dbAdapter, pubSub) { +export function addModel(registry) { + const { dbAdapter, pubSub } = registry; + return class User { static PROFILE_PICTURE_SIZE_LARGE = 75; static PROFILE_PICTURE_SIZE_MEDIUM = 50; @@ -811,7 +812,11 @@ export function addModel(registry, dbAdapter, pubSub) { await Promise.all(promises); monitor.increment('users.bans'); - await EventService.onUserBanned(this.intId, user.intId, bannedUserHasRequestedSubscription); + await registry.eventService.onUserBanned( + this.intId, + user.intId, + bannedUserHasRequestedSubscription, + ); return 1; } @@ -824,7 +829,7 @@ export function addModel(registry, dbAdapter, pubSub) { await dbAdapter.deleteUserBan(this.id, user.id); monitor.increment('users.unbans'); - await EventService.onUserUnbanned(this.intId, user.intId); + await registry.eventService.onUserUnbanned(this.intId, user.intId); return 1; } @@ -856,9 +861,9 @@ export function addModel(registry, dbAdapter, pubSub) { if (!noEvents) { if (targetUser.isUser()) { - await EventService.onUserSubscribed(this.intId, targetUser.intId); + await registry.eventService.onUserSubscribed(this.intId, targetUser.intId); } else { - await EventService.onGroupSubscribed(this.intId, targetUser); + await registry.eventService.onGroupSubscribed(this.intId, targetUser); } } @@ -890,9 +895,9 @@ export function addModel(registry, dbAdapter, pubSub) { monitor.increment('users.unsubscriptions'); if (targetUser.isUser()) { - await EventService.onUserUnsubscribed(this.intId, targetUser.intId); + await registry.eventService.onUserUnsubscribed(this.intId, targetUser.intId); } else { - await EventService.onGroupUnsubscribed(this.intId, targetUser); + await registry.eventService.onGroupUnsubscribed(this.intId, targetUser); } return true; @@ -1198,13 +1203,13 @@ export function addModel(registry, dbAdapter, pubSub) { await fromUser.subscribeTo(this, { homeFeedIds: request.homefeed_ids }); if (this.isGroup()) { - await EventService.onGroupSubscriptionRequestApproved( + await registry.eventService.onGroupSubscriptionRequestApproved( acceptedBy.intId, this, fromUser.intId, ); } else { - await EventService.onSubscriptionRequestApproved(fromUser.intId, this.intId); + await registry.eventService.onSubscriptionRequestApproved(fromUser.intId, this.intId); } return true; diff --git a/app/support/EventService.ts b/app/support/EventService.ts index 56f17818c..12438815b 100644 --- a/app/support/EventService.ts +++ b/app/support/EventService.ts @@ -1,6 +1,7 @@ import _ from 'lodash'; -import { dbAdapter, User, Group, Post, Comment, PubSub as pubSub, Timeline } from '../models'; +import { User, Group, Post, Comment, PubSub as pubSub, Timeline } from '../models'; +import { type ModelsRegistry } from '../models-registry'; import { extractMentions, extractMentionsWithOffsets } from './mentions'; import { @@ -18,14 +19,20 @@ type OnPostFeedsChangedParams = { }; export class EventService { - static async onUserBanned( + readonly registry: ModelsRegistry; + + constructor(registry: ModelsRegistry) { + this.registry = registry; + } + + async onUserBanned( initiatorIntId: number, bannedUserIntId: number, hasRequestedSubscription = false, ) { await Promise.all([ - createEvent(initiatorIntId, EVENT_TYPES.USER_BANNED, initiatorIntId, bannedUserIntId), - createEvent(bannedUserIntId, EVENT_TYPES.BANNED_BY, initiatorIntId, bannedUserIntId), + this.createEvent(initiatorIntId, EVENT_TYPES.USER_BANNED, initiatorIntId, bannedUserIntId), + this.createEvent(bannedUserIntId, EVENT_TYPES.BANNED_BY, initiatorIntId, bannedUserIntId), ]); if (hasRequestedSubscription) { @@ -33,15 +40,25 @@ export class EventService { } } - static async onUserUnbanned(initiatorIntId: number, unbannedUserIntId: number) { + async onUserUnbanned(initiatorIntId: number, unbannedUserIntId: number) { await Promise.all([ - createEvent(initiatorIntId, EVENT_TYPES.USER_UNBANNED, initiatorIntId, unbannedUserIntId), - createEvent(unbannedUserIntId, EVENT_TYPES.UNBANNED_BY, initiatorIntId, unbannedUserIntId), + this.createEvent( + initiatorIntId, + EVENT_TYPES.USER_UNBANNED, + initiatorIntId, + unbannedUserIntId, + ), + this.createEvent( + unbannedUserIntId, + EVENT_TYPES.UNBANNED_BY, + initiatorIntId, + unbannedUserIntId, + ), ]); } - static async onUserSubscribed(initiatorIntId: number, subscribedUserIntId: number) { - await createEvent( + async onUserSubscribed(initiatorIntId: number, subscribedUserIntId: number) { + await this.createEvent( subscribedUserIntId, EVENT_TYPES.USER_SUBSCRIBED, initiatorIntId, @@ -49,8 +66,8 @@ export class EventService { ); } - static async onUserUnsubscribed(initiatorIntId: number, unsubscribedUserIntId: number) { - await createEvent( + async onUserUnsubscribed(initiatorIntId: number, unsubscribedUserIntId: number) { + await this.createEvent( unsubscribedUserIntId, EVENT_TYPES.USER_UNSUBSCRIBED, initiatorIntId, @@ -58,12 +75,17 @@ export class EventService { ); } - static async onSubscriptionRequestCreated(fromUserIntId: number, toUserIntId: number) { - await createEvent(toUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUESTED, fromUserIntId, toUserIntId); + async onSubscriptionRequestCreated(fromUserIntId: number, toUserIntId: number) { + await this.createEvent( + toUserIntId, + EVENT_TYPES.SUBSCRIPTION_REQUESTED, + fromUserIntId, + toUserIntId, + ); } - static async onSubscriptionRequestRevoked(fromUserIntId: number, toUserIntId: number) { - await createEvent( + async onSubscriptionRequestRevoked(fromUserIntId: number, toUserIntId: number) { + await this.createEvent( toUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_REVOKED, fromUserIntId, @@ -71,8 +93,8 @@ export class EventService { ); } - static async onSubscriptionRequestApproved(fromUserIntId: number, toUserIntId: number) { - await createEvent( + async onSubscriptionRequestApproved(fromUserIntId: number, toUserIntId: number) { + await this.createEvent( fromUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_APPROVED, toUserIntId, @@ -80,8 +102,8 @@ export class EventService { ); } - static async onSubscriptionRequestRejected(fromUserIntId: number, toUserIntId: number) { - await createEvent( + async onSubscriptionRequestRejected(fromUserIntId: number, toUserIntId: number) { + await this.createEvent( fromUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_REJECTED, toUserIntId, @@ -89,13 +111,13 @@ export class EventService { ); } - static async onGroupCreated(ownerIntId: number, groupIntId: number) { - await createEvent(ownerIntId, EVENT_TYPES.GROUP_CREATED, ownerIntId, null, groupIntId); + async onGroupCreated(ownerIntId: number, groupIntId: number) { + await this.createEvent(ownerIntId, EVENT_TYPES.GROUP_CREATED, ownerIntId, null, groupIntId); } - static async onGroupSubscribed(initiatorIntId: number, subscribedGroup: Group) { + async onGroupSubscribed(initiatorIntId: number, subscribedGroup: Group) { await this._notifyGroupAdmins(subscribedGroup, (adminUser: User) => - createEvent( + this.createEvent( adminUser.intId, EVENT_TYPES.GROUP_SUBSCRIBED, initiatorIntId, @@ -105,9 +127,9 @@ export class EventService { ); } - static async onGroupUnsubscribed(initiatorIntId: number, unsubscribedGroup: Group) { + async onGroupUnsubscribed(initiatorIntId: number, unsubscribedGroup: Group) { await this._notifyGroupAdmins(unsubscribedGroup, (adminUser: User) => - createEvent( + this.createEvent( adminUser.intId, EVENT_TYPES.GROUP_UNSUBSCRIBED, initiatorIntId, @@ -117,10 +139,10 @@ export class EventService { ); } - static async onGroupAdminPromoted(initiatorIntId: number, group: Group, newAdminIntId: number) { + async onGroupAdminPromoted(initiatorIntId: number, group: Group, newAdminIntId: number) { await this._notifyGroupAdmins(group, async (adminUser: User) => { if (adminUser.intId !== newAdminIntId) { - await createEvent( + await this.createEvent( adminUser.intId, EVENT_TYPES.GROUP_ADMIN_PROMOTED, initiatorIntId, @@ -131,10 +153,10 @@ export class EventService { }); } - static async onGroupAdminDemoted(initiatorIntId: number, group: Group, formerAdminIntId: number) { + async onGroupAdminDemoted(initiatorIntId: number, group: Group, formerAdminIntId: number) { await this._notifyGroupAdmins(group, async (adminUser: User) => { if (adminUser.intId !== formerAdminIntId) { - await createEvent( + await this.createEvent( adminUser.intId, EVENT_TYPES.GROUP_ADMIN_DEMOTED, initiatorIntId, @@ -145,9 +167,9 @@ export class EventService { }); } - static async onGroupSubscriptionRequestCreated(initiatorIntId: number, group: Group) { + async onGroupSubscriptionRequestCreated(initiatorIntId: number, group: Group) { await this._notifyGroupAdmins(group, (adminUser: User) => - createEvent( + this.createEvent( adminUser.intId, EVENT_TYPES.GROUP_SUBSCRIPTION_REQUEST, initiatorIntId, @@ -157,9 +179,9 @@ export class EventService { ); } - static async onGroupSubscriptionRequestRevoked(initiatorIntId: number, group: Group) { + async onGroupSubscriptionRequestRevoked(initiatorIntId: number, group: Group) { await this._notifyGroupAdmins(group, (adminUser: User) => - createEvent( + this.createEvent( adminUser.intId, EVENT_TYPES.GROUP_REQUEST_REVOKED, initiatorIntId, @@ -169,13 +191,13 @@ export class EventService { ); } - static async onGroupSubscriptionRequestApproved( + async onGroupSubscriptionRequestApproved( adminIntId: number, group: Group, requesterIntId: number, ) { await this._notifyGroupAdmins(group, (adminUser: User) => - createEvent( + this.createEvent( adminUser.intId, EVENT_TYPES.MANAGED_GROUP_SUBSCRIPTION_APPROVED, adminIntId, @@ -183,7 +205,7 @@ export class EventService { group.intId, ), ); - await createEvent( + await this.createEvent( requesterIntId, EVENT_TYPES.GROUP_SUBSCRIPTION_APPROVED, adminIntId, @@ -192,13 +214,13 @@ export class EventService { ); } - static async onGroupSubscriptionRequestRejected( + async onGroupSubscriptionRequestRejected( adminIntId: number, group: Group, requesterIntId: number, ) { await this._notifyGroupAdmins(group, (adminUser: User) => - createEvent( + this.createEvent( adminUser.intId, EVENT_TYPES.MANAGED_GROUP_SUBSCRIPTION_REJECTED, adminIntId, @@ -206,7 +228,7 @@ export class EventService { group.intId, ), ); - await createEvent( + await this.createEvent( requesterIntId, EVENT_TYPES.GROUP_SUBSCRIPTION_REJECTED, null, @@ -215,26 +237,26 @@ export class EventService { ); } - static async onPostCreated(post: Post, destinationFeedIds: UUID[], author: User) { - const destinationFeeds = await dbAdapter.getTimelinesByIds(destinationFeedIds); + async onPostCreated(post: Post, destinationFeedIds: UUID[], author: User) { + const destinationFeeds = await this.registry.dbAdapter.getTimelinesByIds(destinationFeedIds); await this._processDirectMessagesForPost(post, destinationFeeds, author); await this._processMentionsInPost(post, destinationFeeds, author); - await processBacklinks(post); + await this.processBacklinks(post); } - static async onCommentChanged(comment: Comment, wasCreated = false) { + async onCommentChanged(comment: Comment, wasCreated = false) { const [post, mentionEvents] = await Promise.all([ comment.getPost(), - getMentionEvents( + this.getMentionEvents( comment.body, comment.userId, EVENT_TYPES.MENTION_IN_COMMENT, EVENT_TYPES.MENTION_COMMENT_TO, ), - processBacklinks(comment), + this.processBacklinks(comment), ]); const directEvents = wasCreated - ? await getDirectEvents(post, comment.userId, EVENT_TYPES.DIRECT_COMMENT_CREATED) + ? await this.getDirectEvents(post, comment.userId, EVENT_TYPES.DIRECT_COMMENT_CREATED) : []; if (mentionEvents.length === 0 && directEvents.length === 0) { @@ -242,9 +264,9 @@ export class EventService { } const [postAuthor, commentAuthor, commentAuthorBanners, destFeeds] = await Promise.all([ - dbAdapter.getUserById(post.userId), - comment.userId ? dbAdapter.getUserById(comment.userId) : null, - dbAdapter.getUserIdsWhoBannedUser(comment.userId!), + this.registry.dbAdapter.getUserById(post.userId), + comment.userId ? this.registry.dbAdapter.getUserById(comment.userId) : null, + this.registry.dbAdapter.getUserIdsWhoBannedUser(comment.userId!), post.getPostedTo(), ]); @@ -273,7 +295,7 @@ export class EventService { [...mentionEvents, ...directEvents] .filter(({ user }) => affectedUsers.some((u) => u.id === user.id)) .map(({ event, user }) => - createEvent( + this.createEvent( user.intId, event, commentAuthor!.intId, @@ -287,20 +309,20 @@ export class EventService { ); } - static async onCommentDestroyed(comment: Comment, destroyedBy: User) { + async onCommentDestroyed(comment: Comment, destroyedBy: User) { if (destroyedBy.id === comment.userId) { return; } const [post, commentAuthor, destroyerGroups] = await Promise.all([ comment.getPost(), - comment.userId ? dbAdapter.getUserById(comment.userId) : null, + comment.userId ? this.registry.dbAdapter.getUserById(comment.userId) : null, destroyedBy.getManagedGroups(), ]); const [postGroupAdmins, postAuthor, postGroups] = await Promise.all([ - dbAdapter.getAdminsOfPostGroups(post.id), - dbAdapter.getUserById(post.userId), + this.registry.dbAdapter.getAdminsOfPostGroups(post.id), + this.registry.dbAdapter.getUserById(post.userId), post.getGroupsPostedTo(), ]); @@ -308,7 +330,7 @@ export class EventService { if (commentAuthor) { // Is post belongs to any group managed by destroyer? const groups = _.intersectionBy(destroyerGroups, postGroups, 'id'); - await createEvent( + await this.createEvent( commentAuthor.intId, EVENT_TYPES.COMMENT_MODERATED, destroyedBy.intId, @@ -333,7 +355,7 @@ export class EventService { otherAdmins.map(async (a) => { const managedGroups = await a.getManagedGroups(); const groups = _.intersectionBy(managedGroups, postGroups, 'id'); - return createEvent( + return this.createEvent( a.intId, EVENT_TYPES.COMMENT_MODERATED_BY_ANOTHER_ADMIN, destroyedBy.intId, @@ -347,17 +369,17 @@ export class EventService { ); } - static async onPostDestroyed(post: Post, destroyedBy: User, params: { groups?: Group[] } = {}) { + async onPostDestroyed(post: Post, destroyedBy: User, params: { groups?: Group[] } = {}) { const { groups: postGroups = [] } = params; if (destroyedBy.id === post.userId) { return; } - const postAuthor = await dbAdapter.getUserById(post.userId); + const postAuthor = await this.registry.dbAdapter.getUserById(post.userId); // Message to the post author - await createEvent( + await this.createEvent( postAuthor!.intId, EVENT_TYPES.POST_MODERATED, destroyedBy.intId, @@ -382,7 +404,7 @@ export class EventService { otherAdmins.map(async (a) => { const managedGroups = await a.getManagedGroups(); const groups = _.intersectionBy(managedGroups, postGroups, 'id'); - return createEvent( + return this.createEvent( a.intId, EVENT_TYPES.POST_MODERATED_BY_ANOTHER_ADMIN, destroyedBy.intId, @@ -396,11 +418,7 @@ export class EventService { ); } - static async onPostFeedsChanged( - post: Post, - changedBy: User, - params: OnPostFeedsChangedParams = {}, - ) { + async onPostFeedsChanged(post: Post, changedBy: User, params: OnPostFeedsChangedParams = {}) { const { addedFeeds = [], removedFeeds = [] } = params; if (addedFeeds.length > 0) { @@ -417,10 +435,10 @@ export class EventService { ); const removedFromGroups = removedFeedOwners.filter((o) => o.isGroup()) as Group[]; - const postAuthor = await dbAdapter.getUserById(post.userId); + const postAuthor = await this.registry.dbAdapter.getUserById(post.userId); // Message to the post author - await createEvent( + await this.createEvent( postAuthor!.intId, EVENT_TYPES.POST_MODERATED, changedBy.intId, @@ -445,7 +463,7 @@ export class EventService { otherAdmins.map(async (a) => { const managedGroups = await a.getManagedGroups(); const groups = _.intersectionBy(managedGroups, removedFromGroups, 'id'); - return createEvent( + return this.createEvent( a.intId, EVENT_TYPES.POST_MODERATED_BY_ANOTHER_ADMIN, changedBy.intId, @@ -459,26 +477,28 @@ export class EventService { ); } - static async onInvitationUsed(fromUserIntId: number, newUserIntId: number) { - await createEvent(fromUserIntId, EVENT_TYPES.INVITATION_USED, newUserIntId, newUserIntId); + async onInvitationUsed(fromUserIntId: number, newUserIntId: number) { + await this.createEvent(fromUserIntId, EVENT_TYPES.INVITATION_USED, newUserIntId, newUserIntId); } - static async onDirectLeft(postId: UUID, initiator: User) { - const post = await dbAdapter.getPostById(postId); + async onDirectLeft(postId: UUID, initiator: User) { + const post = await this.registry.dbAdapter.getPostById(postId); if (!post) { return; } // Posts can have non-unique destinationFeedIds, so we need to _.uniq them - const postFeeds = await dbAdapter.getTimelinesByIntIds(_.uniq(post.destinationFeedIds)); + const postFeeds = await this.registry.dbAdapter.getTimelinesByIntIds( + _.uniq(post.destinationFeedIds), + ); const participantIds = postFeeds.filter((f) => f.isDirects()).map((f) => f.userId); - const participants = await dbAdapter.getUsersByIds(participantIds); + const participants = await this.registry.dbAdapter.getUsersByIds(participantIds); const postAuthor = participants.find((u) => u.id === post.userId); await Promise.all( [initiator, ...participants].map((user) => - createEvent( + this.createEvent( user.intId, EVENT_TYPES.DIRECT_LEFT, initiator.intId, @@ -494,7 +514,7 @@ export class EventService { //////////////////////////////////////////// - static async _processDirectMessagesForPost( + private async _processDirectMessagesForPost( post: Post, destinationFeeds: Timeline[], author: User, @@ -508,10 +528,10 @@ export class EventService { return f.userId; }); - const directReceivers = await dbAdapter.getUsersByIds(directReceiversIds); + const directReceivers = await this.registry.dbAdapter.getUsersByIds(directReceiversIds); await Promise.all( directReceivers.map((receiver) => - createEvent( + this.createEvent( receiver.intId, EVENT_TYPES.DIRECT_CREATED, author.intId, @@ -526,7 +546,7 @@ export class EventService { } } - static async _processMentionsInPost(post: Post, destinationFeeds: Timeline[], author: User) { + private async _processMentionsInPost(post: Post, destinationFeeds: Timeline[], author: User) { const mentionedUsernames = _.uniq(extractMentions(post.body)); if (mentionedUsernames.length === 0) { @@ -548,15 +568,20 @@ export class EventService { const nonDirectFeeds = destinationFeeds.filter((f) => !f.isDirects()); const nonDirectFeedsIds = nonDirectFeeds.map((f) => f.id); const nonDirectFeedsOwnerIds = nonDirectFeeds.map((f) => f.userId); - const postIsPublic = await dbAdapter.someUsersArePublic(nonDirectFeedsOwnerIds, false); + const postIsPublic = await this.registry.dbAdapter.someUsersArePublic( + nonDirectFeedsOwnerIds, + false, + ); const usersBannedByPostAuthor = await author.getBanIds(); - const mentionedUsers = await dbAdapter.getFeedOwnersByUsernames(mentionedUsernames); + const mentionedUsers = await this.registry.dbAdapter.getFeedOwnersByUsernames( + mentionedUsernames, + ); let usersSubscriptionsStatus = [] as { uid: UUID; is_subscribed: boolean }[]; if (!postIsPublic) { - usersSubscriptionsStatus = await dbAdapter.areUsersSubscribedToOneOfTimelines( + usersSubscriptionsStatus = await this.registry.dbAdapter.areUsersSubscribedToOneOfTimelines( mentionedUsers.map((u) => u.id), nonDirectFeedsIds, ); @@ -595,7 +620,7 @@ export class EventService { } } - await createEvent( + await this.createEvent( user.intId, EVENT_TYPES.MENTION_IN_POST, author.intId, @@ -609,173 +634,172 @@ export class EventService { await Promise.all(promises); } - static async _notifyGroupAdmins(group: Group, adminNotifier: (admin: User) => Promise) { - const groupAdminsIds = await dbAdapter.getGroupAdministratorsIds(group.id); - const admins = await dbAdapter.getUsersByIds(groupAdminsIds); + private async _notifyGroupAdmins(group: Group, adminNotifier: (admin: User) => Promise) { + const groupAdminsIds = await this.registry.dbAdapter.getGroupAdministratorsIds(group.id); + const admins = await this.registry.dbAdapter.getUsersByIds(groupAdminsIds); const promises = admins.map((adminUser) => { return adminNotifier(adminUser); }); await Promise.all(promises); } -} -async function getMentionEvents( - text: string, - authorId: Nullable, - eventType: T_EVENT_TYPE, - firstMentionEventType = eventType, -) { - const mentions = _.uniqBy(extractMentionsWithOffsets(text), 'username'); - const mentionedUsers = (await dbAdapter.getFeedOwnersByUsernames(mentions.map((u) => u.username))) - // Only users (not groups) and not an event author - .filter((u) => u.isUser() && u.id !== authorId) as User[]; - return mentions - .map(({ username, offset }) => ({ - event: offset === 0 ? firstMentionEventType : eventType, - user: mentionedUsers.find((u) => u.username === username), - })) - .filter(({ user }) => !!user) as { user: User; event: T_EVENT_TYPE }[]; -} + async getMentionEvents( + text: string, + authorId: Nullable, + eventType: T_EVENT_TYPE, + firstMentionEventType = eventType, + ) { + const mentions = _.uniqBy(extractMentionsWithOffsets(text), 'username'); + const mentionedUsers = ( + await this.registry.dbAdapter.getFeedOwnersByUsernames(mentions.map((u) => u.username)) + ) + // Only users (not groups) and not an event author + .filter((u) => u.isUser() && u.id !== authorId) as User[]; + return mentions + .map(({ username, offset }) => ({ + event: offset === 0 ? firstMentionEventType : eventType, + user: mentionedUsers.find((u) => u.username === username), + })) + .filter(({ user }) => !!user) as { user: User; event: T_EVENT_TYPE }[]; + } -async function getDirectEvents(post: Post, authorId: Nullable, eventType: T_EVENT_TYPE) { - const destFeeds = await post.getPostedTo(); - const directFeeds = destFeeds.filter((f) => f.isDirects() && f.userId !== authorId); - const directReceivers = await dbAdapter.getUsersByIds(directFeeds.map((f) => f.userId)); - return directReceivers.map((user) => ({ event: eventType, user })); -} + async getDirectEvents(post: Post, authorId: Nullable, eventType: T_EVENT_TYPE) { + const destFeeds = await post.getPostedTo(); + const directFeeds = destFeeds.filter((f) => f.isDirects() && f.userId !== authorId); + const directReceivers = await this.registry.dbAdapter.getUsersByIds( + directFeeds.map((f) => f.userId), + ); + return directReceivers.map((user) => ({ event: eventType, user })); + } -async function processBacklinks(srcEntity: Post | Comment) { - const uuids = extractUUIDs(srcEntity.body); - const [mentionedPosts, mentionedComments] = await Promise.all([ - dbAdapter.getPostsByIds(uuids), - dbAdapter.getCommentsByIds(uuids), - ]); + async processBacklinks(srcEntity: Post | Comment) { + const uuids = extractUUIDs(srcEntity.body); + const [mentionedPosts, mentionedComments] = await Promise.all([ + this.registry.dbAdapter.getPostsByIds(uuids), + this.registry.dbAdapter.getCommentsByIds(uuids), + ]); - if (mentionedPosts.length === 0 && mentionedComments.length === 0) { - return; - } + if (mentionedPosts.length === 0 && mentionedComments.length === 0) { + return; + } - const [srcViewers, initiator] = await Promise.all([ - srcEntity.usersCanSee(), - srcEntity.getCreatedBy(), - ]); + const [srcViewers, initiator] = await Promise.all([ + srcEntity.usersCanSee(), + srcEntity.getCreatedBy(), + ]); - const srcPost = srcEntity instanceof Post ? srcEntity : await srcEntity.getPost(); - const [srcPostAuthor, srcPostGroups] = await Promise.all([ - srcPost.getCreatedBy(), - srcPost.getGroupsPostedTo(), - ]); + const srcPost = srcEntity instanceof Post ? srcEntity : await srcEntity.getPost(); + const [srcPostAuthor, srcPostGroups] = await Promise.all([ + srcPost.getCreatedBy(), + srcPost.getGroupsPostedTo(), + ]); - await Promise.all([ - ...mentionedPosts.map(async (post) => { - if (srcEntity.userId === post.userId || !srcViewers.includes(post.userId)) { - return; - } + await Promise.all([ + ...mentionedPosts.map(async (post) => { + if (srcEntity.userId === post.userId || !srcViewers.includes(post.userId)) { + return; + } - const postAuthor = await post.getCreatedBy(); + const postAuthor = await post.getCreatedBy(); - await createEvent( - postAuthor.intId, - srcEntity instanceof Post ? EVENT_TYPES.BACKLINK_IN_POST : EVENT_TYPES.BACKLINK_IN_COMMENT, - initiator.intId, - postAuthor.intId, - srcPostGroups[0]?.intId, - srcPost.id, - srcEntity instanceof Comment ? srcEntity.id : null, - srcPostAuthor.intId, - post.id, - ); - }), - ...mentionedComments.map(async (comment) => { - if ( - !comment.userId || - srcEntity.userId === comment.userId || - !srcViewers.includes(comment.userId) - ) { - return; - } + await this.createEvent( + postAuthor.intId, + srcEntity instanceof Post + ? EVENT_TYPES.BACKLINK_IN_POST + : EVENT_TYPES.BACKLINK_IN_COMMENT, + initiator.intId, + postAuthor.intId, + srcPostGroups[0]?.intId, + srcPost.id, + srcEntity instanceof Comment ? srcEntity.id : null, + srcPostAuthor.intId, + post.id, + ); + }), + ...mentionedComments.map(async (comment) => { + if ( + !comment.userId || + srcEntity.userId === comment.userId || + !srcViewers.includes(comment.userId) + ) { + return; + } - const [commentAuthor, commentPost] = await Promise.all([ - comment.getCreatedBy(), - comment.getPost(), - ]); + const [commentAuthor, commentPost] = await Promise.all([ + comment.getCreatedBy(), + comment.getPost(), + ]); - await createEvent( - commentAuthor.intId, - srcEntity instanceof Post ? EVENT_TYPES.BACKLINK_IN_POST : EVENT_TYPES.BACKLINK_IN_COMMENT, - initiator.intId, - commentAuthor.intId, - srcPostGroups[0]?.intId, - srcPost.id, - srcEntity instanceof Comment ? srcEntity.id : null, - srcPostAuthor.intId, - commentPost.id, - comment.id, - ); - }), - ]); -} + await this.createEvent( + commentAuthor.intId, + srcEntity instanceof Post + ? EVENT_TYPES.BACKLINK_IN_POST + : EVENT_TYPES.BACKLINK_IN_COMMENT, + initiator.intId, + commentAuthor.intId, + srcPostGroups[0]?.intId, + srcPost.id, + srcEntity instanceof Comment ? srcEntity.id : null, + srcPostAuthor.intId, + commentPost.id, + comment.id, + ); + }), + ]); + } + + /** + * Create event and perform necessary actions after create + */ + async createEvent( + recipientIntId: number, + eventType: T_EVENT_TYPE, + createdByUserIntId: Nullable, + targetUserIntId: Nullable = null, + groupIntId: Nullable = null, + postId: Nullable = null, + commentId: Nullable = null, + postAuthorIntId: Nullable = null, + targetPostId: Nullable = null, + targetCommentId: Nullable = null, + ) { + const event = await this.registry.dbAdapter.createEvent( + recipientIntId, + eventType, + createdByUserIntId, + targetUserIntId, + groupIntId, + postId, + commentId, + postAuthorIntId, + targetPostId, + targetCommentId, + ); + + // It is possible if event is conflicting with existing by unique key + if (!event) { + return null; + } + + const updates = []; + + if (ALLOWED_EVENT_TYPES.includes(eventType)) { + updates.push(pubSub.newEvent(event.uid)); + } -/** - * Create event and perform necessary actions after create - * - * @param recipientIntId - * @param eventType - * @param createdByUserIntId - * @param targetUserIntId - * @param groupIntId - * @param postId - * @param commentId - * @param postAuthorIntId - */ -async function createEvent( - recipientIntId: number, - eventType: T_EVENT_TYPE, - createdByUserIntId: Nullable, - targetUserIntId: Nullable = null, - groupIntId: Nullable = null, - postId: Nullable = null, - commentId: Nullable = null, - postAuthorIntId: Nullable = null, - targetPostId: Nullable = null, - targetCommentId: Nullable = null, -) { - const event = await dbAdapter.createEvent( - recipientIntId, - eventType, - createdByUserIntId, - targetUserIntId, - groupIntId, - postId, - commentId, - postAuthorIntId, - targetPostId, - targetCommentId, - ); - - // It is possible if event is conflicting with existing by unique key - if (!event) { - return null; - } - - const updates = []; - - if (ALLOWED_EVENT_TYPES.includes(eventType)) { - updates.push(pubSub.newEvent(event.uid)); - } - - if (COUNTABLE_EVENT_TYPES.includes(eventType)) { - if (recipientIntId !== createdByUserIntId) { - updates.push(pubSub.updateUnreadNotifications(recipientIntId)); - - if (eventType === EVENT_TYPES.SUBSCRIPTION_REQUEST_APPROVED) { - updates.push(pubSub.updateUnreadNotifications(createdByUserIntId!)); + if (COUNTABLE_EVENT_TYPES.includes(eventType)) { + if (recipientIntId !== createdByUserIntId) { + updates.push(pubSub.updateUnreadNotifications(recipientIntId)); + + if (eventType === EVENT_TYPES.SUBSCRIPTION_REQUEST_APPROVED) { + updates.push(pubSub.updateUnreadNotifications(createdByUserIntId!)); + } } } - } - await Promise.all(updates); + await Promise.all(updates); - return event; + return event; + } } From 66b14eda82aa705244cd8a783489e7e7f153c74f Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sun, 15 May 2022 19:02:45 +0300 Subject: [PATCH 14/15] Decouple PubsubListener from globals --- app/app.js | 2 +- app/models/comment.js | 2 +- app/models/post.js | 13 ++-- app/pubsub-listener.js | 157 ++++++++++++++++++++++------------------- 4 files changed, 95 insertions(+), 79 deletions(-) diff --git a/app/app.js b/app/app.js index f2d1e66df..bb8f69b84 100644 --- a/app/app.js +++ b/app/app.js @@ -36,7 +36,7 @@ export async function getSingleton() { const server = http.createServer(_app.callback()); const listen = util.promisify(server.listen).bind(server); - _app.context.pubsub = new PubsubListener(server, _app); + _app.context.pubsub = new PubsubListener(_app.context.modelRegistry, server, _app); const port = process.env.PEPYATKA_SERVER_PORT || process.env.PORT || _app.context.config.port; await listen(port); diff --git a/app/models/comment.js b/app/models/comment.js index 9ff664ed3..7be0f99c0 100644 --- a/app/models/comment.js +++ b/app/models/comment.js @@ -197,7 +197,7 @@ export function addModel(registry) { async destroy(destroyedBy = null) { const post = await this.getPost(); - const realtimeRooms = await getRoomsOfPost(post); + const realtimeRooms = await getRoomsOfPost(dbAdapter, post); const notifyBacklinked = await notifyBacklinkedLater( this, pubSub, diff --git a/app/models/post.js b/app/models/post.js index 8b8405c6a..bc5ad3236 100644 --- a/app/models/post.js +++ b/app/models/post.js @@ -192,7 +192,7 @@ export function addModel(registry) { const payload = { updatedAt: this.updatedAt.toString() }; const afterUpdate = []; - let realtimeRooms = await getRoomsOfPost(this); + let realtimeRooms = await getRoomsOfPost(dbAdapter, this); const usersCanSeePostBeforeIds = await this.usersCanSee(); if (params.body != null) { @@ -251,7 +251,7 @@ export function addModel(registry) { // Publishing changes to the old AND new realtime rooms afterUpdate.push(async () => { - const rooms = await getRoomsOfPost(this); + const rooms = await getRoomsOfPost(dbAdapter, this); realtimeRooms = _.union(realtimeRooms, rooms); }); } @@ -298,7 +298,7 @@ export function addModel(registry) { async destroy(destroyedBy = null) { const [realtimeRooms, comments, groups, notifyBacklinked] = await Promise.all([ - getRoomsOfPost(this), + getRoomsOfPost(dbAdapter, this), this.getComments(), this.getGroupsPostedTo(), notifyBacklinkedLater(this, pubSub, getUpdatedUUIDs(this.body)), @@ -786,7 +786,7 @@ export function addModel(registry) { } const [realtimeRooms, timelineId, ,] = await Promise.all([ - getRoomsOfPost(this), + getRoomsOfPost(dbAdapter, this), user.getLikesTimelineIntId(), dbAdapter.statsLikeDeleted(user.id), ]); @@ -961,7 +961,10 @@ export function addModel(registry) { const userDirectsFeed = await user.getDirectsTimeline(); // Get realtime parameters before changes - const [rooms, usersBeforeIds] = await Promise.all([getRoomsOfPost(this), this.usersCanSee()]); + const [rooms, usersBeforeIds] = await Promise.all([ + getRoomsOfPost(dbAdapter, this), + this.usersCanSee(), + ]); const ok = await dbAdapter.withdrawPostFromDestFeed(userDirectsFeed?.intId, this.id); diff --git a/app/pubsub-listener.js b/app/pubsub-listener.js index 572254776..7f2391df2 100644 --- a/app/pubsub-listener.js +++ b/app/pubsub-listener.js @@ -22,7 +22,6 @@ import createDebug from 'debug'; import Raven from 'raven'; import config from 'config'; -import { registry, dbAdapter, Comment } from './models'; import { eventNames } from './support/PubSubAdapter'; import { List } from './support/open-lists'; import { withAuthToken } from './controllers/middlewares/with-auth-token'; @@ -41,10 +40,17 @@ const sentryIsEnabled = 'sentryDsn' in config; const debug = createDebug('freefeed:PubsubListener'); export default class PubsubListener { + registry; app; io; - constructor(server, app) { + /** + * @param {ModelsRegistry} registry + * @param server + * @param app + */ + constructor(registry, server, app) { + this.registry = registry; this.app = app; const pubClient = new Redis({ @@ -73,7 +79,7 @@ export default class PubsubListener { // authentication this.io.use(async (socket, next) => { try { - socket.userId = await getAuthUserId(socket.handshake.query.token, socket); + socket.userId = await this.getAuthUserId(socket.handshake.query.token, socket); socket.authToken = socket.handshake.query.token; debug(`[socket.id=${socket.id}] auth user`, socket.userId); } catch (e) { @@ -118,7 +124,7 @@ export default class PubsubListener { throw new EventHandlingError('request without data'); } - socket.userId = await getAuthUserId(data.authToken, socket); + socket.userId = await this.getAuthUserId(data.authToken, socket); socket.authToken = data.authToken; debug(`[socket.id=${socket.id}] auth user`, socket.userId); }); @@ -137,7 +143,7 @@ export default class PubsubListener { const [objId] = channelId.split('?', 2); // channelId may have params after '?' if (channelType === 'timeline') { - const t = await dbAdapter.getTimelineById(objId); + const t = await this.registry.dbAdapter.getTimelineById(objId); if (!t) { throw new EventHandlingError( @@ -273,6 +279,7 @@ export default class PubsubListener { return; } + const { dbAdapter, Comment } = this.registry; emitter = this._onlyUsersEmitter(onlyForUsers, emitter); let userIds = destSockets.map((s) => s.userId); @@ -390,10 +397,10 @@ export default class PubsubListener { }; onPostNew = async ({ postId }) => { - const post = await dbAdapter.getPostById(postId); + const post = await this.registry.dbAdapter.getPostById(postId); const json = { postId }; const type = eventNames.POST_CREATED; - const rooms = await getRoomsOfPost(post); + const rooms = await getRoomsOfPost(this.registry.dbAdapter, post); await this.broadcastMessage(rooms, type, json, { post, emitter: this._postEventEmitter }); }; @@ -404,14 +411,14 @@ export default class PubsubListener { // The JSON of List.everything() onlyForUsers = { items: [], inclusive: false }, }) => { - const post = await dbAdapter.getPostById(postId); + const post = await this.registry.dbAdapter.getPostById(postId); if (!post) { return; } if (!rooms) { - rooms = await getRoomsOfPost(post); + rooms = await getRoomsOfPost(this.registry.dbAdapter, post); } const broadcastOptions = { @@ -433,18 +440,18 @@ export default class PubsubListener { }; onCommentNew = async ({ commentId }) => { - const comment = await dbAdapter.getCommentById(commentId); + const comment = await this.registry.dbAdapter.getCommentById(commentId); if (!comment) { // might be outdated event return; } - const post = await dbAdapter.getPostById(comment.postId); + const post = await this.registry.dbAdapter.getPostById(comment.postId); const json = await serializeCommentForRealtime(comment); const type = eventNames.COMMENT_CREATED; - const rooms = await getRoomsOfPost(post); + const rooms = await getRoomsOfPost(this.registry.dbAdapter, post); await this.broadcastMessage(rooms, type, json, { post, emitter: this._commentLikeEventEmitter, @@ -452,12 +459,12 @@ export default class PubsubListener { }; onCommentUpdate = async (data) => { - const comment = await dbAdapter.getCommentById(data.commentId); - const post = await dbAdapter.getPostById(comment.postId); + const comment = await this.registry.dbAdapter.getCommentById(data.commentId); + const post = await this.registry.dbAdapter.getPostById(comment.postId); const json = await serializeCommentForRealtime(comment); const type = eventNames.COMMENT_UPDATED; - const rooms = await getRoomsOfPost(post); + const rooms = await getRoomsOfPost(this.registry.dbAdapter, post); await this.broadcastMessage(rooms, type, json, { post, emitter: this._commentLikeEventEmitter, @@ -466,27 +473,27 @@ export default class PubsubListener { onCommentDestroy = async ({ postId, commentId, rooms }) => { const json = { postId, commentId }; - const post = await dbAdapter.getPostById(postId); + const post = await this.registry.dbAdapter.getPostById(postId); const type = eventNames.COMMENT_DESTROYED; await this.broadcastMessage(rooms, type, json, { post }); }; onLikeNew = async ({ userId, postId }) => { const [user, post] = await Promise.all([ - await dbAdapter.getUserById(userId), - await dbAdapter.getPostById(postId), + await this.registry.dbAdapter.getUserById(userId), + await this.registry.dbAdapter.getPostById(postId), ]); const json = serializeLike(user); json.meta = { postId }; const type = eventNames.LIKE_ADDED; - const rooms = await getRoomsOfPost(post); + const rooms = await getRoomsOfPost(this.registry.dbAdapter, post); await this.broadcastMessage(rooms, type, json, { post }); }; onLikeRemove = async ({ userId, postId, rooms }) => { const json = { meta: { userId, postId } }; - const post = await dbAdapter.getPostById(postId); + const post = await this.registry.dbAdapter.getPostById(postId); const type = eventNames.LIKE_REMOVED; await this.broadcastMessage(rooms, type, json, { post }); }; @@ -495,10 +502,10 @@ export default class PubsubListener { // NOTE: this event only broadcasts to hider's sockets // so it won't leak any personal information const json = { meta: { postId } }; - const post = await dbAdapter.getPostById(postId); + const post = await this.registry.dbAdapter.getPostById(postId); const type = eventNames.POST_HIDDEN; - const rooms = await getRoomsOfPost(post); + const rooms = await getRoomsOfPost(this.registry.dbAdapter, post); await this.broadcastMessage(rooms, type, json, { post, emitter: this._singleUserEmitter(userId), @@ -509,10 +516,10 @@ export default class PubsubListener { // NOTE: this event only broadcasts to hider's sockets // so it won't leak any personal information const json = { meta: { postId } }; - const post = await dbAdapter.getPostById(postId); + const post = await this.registry.dbAdapter.getPostById(postId); const type = eventNames.POST_UNHIDDEN; - const rooms = await getRoomsOfPost(post); + const rooms = await getRoomsOfPost(this.registry.dbAdapter, post); await this.broadcastMessage(rooms, type, json, { post, emitter: this._singleUserEmitter(userId), @@ -523,10 +530,10 @@ export default class PubsubListener { // NOTE: this event only broadcasts to saver's sockets // so it won't leak any personal information const json = { meta: { postId } }; - const post = await dbAdapter.getPostById(postId); + const post = await this.registry.dbAdapter.getPostById(postId); const type = eventNames.POST_SAVED; - const rooms = await getRoomsOfPost(post); + const rooms = await getRoomsOfPost(this.registry.dbAdapter, post); await this.broadcastMessage(rooms, type, json, { post, emitter: this._singleUserEmitter(userId), @@ -537,10 +544,10 @@ export default class PubsubListener { // NOTE: this event only broadcasts to saver's sockets // so it won't leak any personal information const json = { meta: { postId } }; - const post = await dbAdapter.getPostById(postId); + const post = await this.registry.dbAdapter.getPostById(postId); const type = eventNames.POST_UNSAVED; - const rooms = await getRoomsOfPost(post); + const rooms = await getRoomsOfPost(this.registry.dbAdapter, post); await this.broadcastMessage(rooms, type, json, { post, emitter: this._singleUserEmitter(userId), @@ -548,9 +555,9 @@ export default class PubsubListener { }; onEventCreated = async (eventId) => { - const event = await dbAdapter.getEventById(eventId); + const event = await this.registry.dbAdapter.getEventById(eventId); - const [{ uid: userId }] = await dbAdapter.getUsersIdsByIntIds([event.user_id]); + const [{ uid: userId }] = await this.registry.dbAdapter.getUsersIdsByIntIds([event.user_id]); const { events, users, groups } = await serializeEvents([event], userId); @@ -575,7 +582,7 @@ export default class PubsubListener { }; onGlobalUserUpdate = async (userId) => { - const account = await dbAdapter.getFeedOwnerById(userId); + const account = await this.registry.dbAdapter.getFeedOwnerById(userId); if (!account) { return; @@ -585,7 +592,7 @@ export default class PubsubListener { if (account.isGroup() && account.isPrivate === '1') { const postsFeed = await account.getPostsTimeline(); - receivers = List.from(await dbAdapter.getTimelineSubscribersIds(postsFeed.id)); + receivers = List.from(await this.registry.dbAdapter.getTimelineSubscribersIds(postsFeed.id)); } await this.broadcastMessage(['global:users'], eventNames.GLOBAL_USER_UPDATED, null, { @@ -600,16 +607,20 @@ export default class PubsubListener { }); }; onGroupTimesUpdate = async ({ groupIds }) => { - const groups = (await dbAdapter.getFeedOwnersByIds(groupIds)).filter((g) => g.isGroup()); + const groups = (await this.registry.dbAdapter.getFeedOwnersByIds(groupIds)).filter((g) => + g.isGroup(), + ); if (groups.length === 0) { return; } groupIds = groups.map((g) => g.id); - const feedIds = (await dbAdapter.getUsersNamedTimelines(groupIds, 'Posts')).map((f) => f.id); + const feedIds = (await this.registry.dbAdapter.getUsersNamedTimelines(groupIds, 'Posts')).map( + (f) => f.id, + ); - const rooms = (await dbAdapter.getUsersSubscribedToTimelines(feedIds)).map( + const rooms = (await this.registry.dbAdapter.getUsersSubscribedToTimelines(feedIds)).map( (id) => `user:${id}`, ); @@ -624,7 +635,9 @@ export default class PubsubListener { if (groupIds.length > 1) { // User probably not subscribed to all of these groups isSubscribed = await Promise.all( - feedIds.map((id) => dbAdapter.isUserSubscribedToTimeline(socket.userId, id)), + feedIds.map((id) => + this.registry.dbAdapter.isUserSubscribedToTimeline(socket.userId, id), + ), ); } @@ -644,8 +657,8 @@ export default class PubsubListener { // Helpers _sendCommentLikeMsg = async (data, msgType) => { - const comment = await dbAdapter.getCommentById(data.commentId); - const post = await dbAdapter.getPostById(data.postId); + const comment = await this.registry.dbAdapter.getCommentById(data.commentId); + const post = await this.registry.dbAdapter.getPostById(data.postId); if (!comment || !post) { return; @@ -659,23 +672,23 @@ export default class PubsubListener { json.comments.userId = data.unlikerUUID; } - const rooms = await getRoomsOfPost(post); + const rooms = await getRoomsOfPost(this.registry.dbAdapter, post); await this.broadcastMessage(rooms, msgType, json, { post, emitter: this._commentLikeEventEmitter, }); }; - async _commentLikeEventEmitter(socket, type, json) { + _commentLikeEventEmitter = async (socket, type, json) => { const commentUUID = json.comments.id; const viewerId = socket.userId; const [commentLikesData = { c_likes: 0, has_own_like: false }] = - await dbAdapter.getLikesInfoForComments([commentUUID], viewerId); + await this.registry.dbAdapter.getLikesInfoForComments([commentUUID], viewerId); json.comments.likes = parseInt(commentLikesData.c_likes); json.comments.hasOwnLike = commentLikesData.has_own_like; defaultEmitter(socket, type, json); - } + }; _postEventEmitter = async (socket, type, { postId, realtimeChannels }) => { const json = await serializeSinglePost(postId, socket.userId); @@ -718,8 +731,8 @@ export default class PubsubListener { } const [commentLikesData, [commentLikesForPost]] = await Promise.all([ - dbAdapter.getLikesInfoForComments(commentIds, viewerUUID), - dbAdapter.getLikesInfoForPosts([postPayload.posts.id], viewerUUID), + this.registry.dbAdapter.getLikesInfoForComments(commentIds, viewerUUID), + this.registry.dbAdapter.getLikesInfoForPosts([postPayload.posts.id], viewerUUID), ]); const commentLikes = keyBy(commentLikesData, 'uid'); @@ -761,7 +774,7 @@ export default class PubsubListener { let userId = null; try { - userId = await getAuthUserId(socket.authToken, socket); + userId = await this.getAuthUserId(socket.authToken, socket); } catch (e) { // pass } @@ -773,6 +786,32 @@ export default class PubsubListener { }), ); } + + getAuthUserId = async (jwtToken, socket) => { + if (!jwtToken) { + return null; + } + + // Parse the 'X-Forwarded-For' header ("client, proxy1, proxy2") + const proxyHeader = socket.handshake.headers[config.proxyIpHeader.toLowerCase()]; + const ips = config.trustProxyHeaders && proxyHeader ? proxyHeader.split(/\s*,\s*/) : []; + + // Fake context + const ctx = { + ip: ips[0] || socket.handshake.address, + headers: { + ...socket.handshake.headers, + authorization: `Bearer ${jwtToken}`, + }, + method: 'WS', + state: { matchedRoute: '*' }, + modelRegistry: this.registry, + }; + + await withAuthToken(ctx, () => null); + + return ctx.state.authToken.userId; + }; } /** @@ -783,7 +822,7 @@ export default class PubsubListener { * @param {Post} post * @return {Promise} */ -export async function getRoomsOfPost(post) { +export async function getRoomsOfPost(dbAdapter, post) { if (!post) { return []; } @@ -899,29 +938,3 @@ const onSocketEvent = (socket, event, handler) => callback({ success: false, message: e.message }); } }); - -async function getAuthUserId(jwtToken, socket) { - if (!jwtToken) { - return null; - } - - // Parse the 'X-Forwarded-For' header ("client, proxy1, proxy2") - const proxyHeader = socket.handshake.headers[config.proxyIpHeader.toLowerCase()]; - const ips = config.trustProxyHeaders && proxyHeader ? proxyHeader.split(/\s*,\s*/) : []; - - // Fake context - const ctx = { - ip: ips[0] || socket.handshake.address, - headers: { - ...socket.handshake.headers, - authorization: `Bearer ${jwtToken}`, - }, - method: 'WS', - state: { matchedRoute: '*' }, - modelRegistry: registry, - }; - - await withAuthToken(ctx, () => null); - - return ctx.state.authToken.userId; -} From 67afb23e1b0d724cd64e3bdc2b1795452f72f75c Mon Sep 17 00:00:00 2001 From: Alexey Zakhlestin Date: Sun, 15 May 2022 20:00:04 +0300 Subject: [PATCH 15/15] Decouple PubSub from globals --- app/models-registry.ts | 7 ++++--- app/models.d.ts | 4 ++-- app/models.js | 7 +++---- app/pubsub.ts | 15 ++++++++------- app/support/EventService.ts | 2 +- test/functional/all-groups.js | 4 ++-- test/functional/app-tokens.js | 4 ++-- test/functional/archives.js | 4 ++-- test/functional/backlinks.js | 4 ++-- test/functional/bookmarklet.js | 4 ++-- test/functional/comment_likes.js | 4 ++-- test/functional/comments.js | 4 ++-- test/functional/events.js | 12 ++++++------ test/functional/gone-users.js | 4 ++-- test/functional/group-moderation.js | 4 ++-- test/functional/groups.js | 4 ++-- test/functional/hidden-comments.js | 4 ++-- test/functional/invitations.js | 4 ++-- test/functional/multi-homefeeds.js | 4 ++-- test/functional/mutual_friends.js | 4 ++-- test/functional/passwords.js | 4 ++-- test/functional/posts/leave-direct.js | 4 ++-- test/functional/posts/posts.js | 4 ++-- test/functional/posts/postsV2.js | 4 ++-- test/functional/private_groups.js | 4 ++-- test/functional/privates.js | 4 ++-- test/functional/realtime-nitifications.js | 4 ++-- test/functional/realtime.js | 4 ++-- test/functional/realtime2.js | 8 ++++---- test/functional/request_revocation.js | 4 ++-- test/functional/search.js | 4 ++-- test/functional/session.js | 4 ++-- test/functional/summary.js | 4 ++-- test/functional/timeline.js | 4 ++-- test/functional/timelinesV2.js | 4 ++-- test/functional/users.js | 4 ++-- test/functional/usersV2.js | 4 ++-- 37 files changed, 88 insertions(+), 87 deletions(-) diff --git a/app/models-registry.ts b/app/models-registry.ts index 7e7ef9fb7..adb20aca0 100644 --- a/app/models-registry.ts +++ b/app/models-registry.ts @@ -1,7 +1,7 @@ import type Knex from 'knex'; import { DbAdapter } from './support/DbAdapter'; -import type PubSub from './pubsub'; +import PubSub from './pubsub'; import { addModel as attachmentModel } from './models/attachment'; import { addModel as commentModel } from './models/comment'; import { addModel as groupModel } from './models/group'; @@ -22,6 +22,7 @@ import { type JobManager, } from './models'; import { EventService } from './support/EventService'; +import { PubSubAdapter } from './support/PubSubAdapter'; export class ModelsRegistry { readonly dbAdapter: DbAdapter; @@ -38,9 +39,9 @@ export class ModelsRegistry { readonly Job: typeof Job; readonly JobManager: typeof JobManager; - constructor(database: Knex, pubSub: PubSub) { + constructor(database: Knex, pubsubAdapter: PubSubAdapter) { this.dbAdapter = new DbAdapter(database, this); - this.pubSub = pubSub; + this.pubSub = new PubSub(pubsubAdapter, this); this.User = userModel(this); this.Group = groupModel(this, this.dbAdapter, this.pubSub); diff --git a/app/models.d.ts b/app/models.d.ts index 95f35e151..5d48fb63e 100644 --- a/app/models.d.ts +++ b/app/models.d.ts @@ -2,7 +2,7 @@ import Knex from 'knex'; import type Config from 'config'; import { DbAdapter } from './support/DbAdapter'; -import PubSubAdapter from './pubsub'; +import PubSub from './pubsub'; import { GONE_NAMES } from './models/user'; import { Nullable, UUID } from './support/types'; import { SessionTokenV1Store } from './models/auth-tokens'; @@ -11,7 +11,7 @@ import { ModelsRegistry } from './models-registry'; export const postgres: Knex; export const dbAdapter: DbAdapter; -export const PubSub: PubSubAdapter; +export const pubSub: PubSub; export const registry: ModelsRegistry; export class User { diff --git a/app/models.js b/app/models.js index 38d7f5b9c..eaf67e633 100644 --- a/app/models.js +++ b/app/models.js @@ -4,7 +4,7 @@ import config from 'config'; import { connect as redisConnection } from './setup/database'; import { connect as postgresConnection } from './setup/postgres'; import { PubSubAdapter } from './support/PubSubAdapter'; -import pubSub, { DummyPublisher } from './pubsub'; +import { DummyPublisher } from './pubsub'; import { SessionTokenV1Store } from './models/auth-tokens'; import { ModelsRegistry } from './models-registry'; @@ -19,11 +19,10 @@ if (config.disableRealtime) { pubsubAdapter = new PubSubAdapter(redisConnection()); } -export const PubSub = new pubSub(pubsubAdapter); - -export const registry = new ModelsRegistry(postgres, PubSub); +export const registry = new ModelsRegistry(postgres, pubsubAdapter); export const { dbAdapter, + pubSub, User, Group, Post, diff --git a/app/pubsub.ts b/app/pubsub.ts index 46bdad506..ca604deed 100644 --- a/app/pubsub.ts +++ b/app/pubsub.ts @@ -1,8 +1,9 @@ -import { Comment, dbAdapter, Post } from './models'; import { serializeTimeline } from './serializers/v2/timeline'; import { List } from './support/open-lists'; import { PubSubAdapter } from './support/PubSubAdapter'; import { Nullable, UUID } from './support/types'; +import { ModelsRegistry } from './models-registry'; +import { type Post, type Comment } from './models'; export class DummyPublisher extends PubSubAdapter { constructor() { @@ -18,30 +19,30 @@ type UpdatePostOptions = { onlyForUsers?: List; }; -export default class pubSub { - constructor(private publisher: PubSubAdapter) {} +export default class PubSub { + constructor(private publisher: PubSubAdapter, readonly registry: ModelsRegistry) {} setPublisher(publisher: PubSubAdapter) { this.publisher = publisher; } async updateUnreadDirects(userId: UUID) { - const unreadDirectsNumber = await dbAdapter.getUnreadDirectsNumber(userId); + const unreadDirectsNumber = await this.registry.dbAdapter.getUnreadDirectsNumber(userId); const user = { id: userId, unreadDirectsNumber }; const payload = JSON.stringify({ user }); await this.publisher.userUpdated(payload); } async updateUnreadNotifications(userIntId: number) { - const [{ uid: userId }] = await dbAdapter.getUsersIdsByIntIds([userIntId]); - const unreadNotificationsNumber = await dbAdapter.getUnreadEventsNumber(userId); + const [{ uid: userId }] = await this.registry.dbAdapter.getUsersIdsByIntIds([userIntId]); + const unreadNotificationsNumber = await this.registry.dbAdapter.getUnreadEventsNumber(userId); const user = { id: userId, unreadNotificationsNumber }; const payload = JSON.stringify({ user }); await this.publisher.userUpdated(payload); } async updateHomeFeeds(userId: UUID) { - const feedObjects = await dbAdapter.getAllUserNamedFeed(userId, 'RiverOfNews'); + const feedObjects = await this.registry.dbAdapter.getAllUserNamedFeed(userId, 'RiverOfNews'); const payload = JSON.stringify({ homeFeeds: feedObjects.map((f) => serializeTimeline(f)), user: { id: userId }, diff --git a/app/support/EventService.ts b/app/support/EventService.ts index 12438815b..849fa6761 100644 --- a/app/support/EventService.ts +++ b/app/support/EventService.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import { User, Group, Post, Comment, PubSub as pubSub, Timeline } from '../models'; +import { User, Group, Post, Comment, pubSub, Timeline } from '../models'; import { type ModelsRegistry } from '../models-registry'; import { extractMentions, extractMentionsWithOffsets } from './mentions'; diff --git a/test/functional/all-groups.js b/test/functional/all-groups.js index 03890c44f..af834706d 100644 --- a/test/functional/all-groups.js +++ b/test/functional/all-groups.js @@ -7,7 +7,7 @@ import unexpected from 'unexpected'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import * as testHelper from '../functional/functional_test_helper'; import { allGroupsResponse, freefeedAssertions } from './schemaV2-helper'; @@ -18,7 +18,7 @@ describe('All groups', () => { let app; before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); await cleanDB($pg_database); }); diff --git a/test/functional/app-tokens.js b/test/functional/app-tokens.js index 46b29962c..9cd814892 100644 --- a/test/functional/app-tokens.js +++ b/test/functional/app-tokens.js @@ -7,7 +7,7 @@ import config from 'config'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; -import { dbAdapter, PubSub } from '../../app/models'; +import { dbAdapter, pubSub } from '../../app/models'; import { appTokensScopes, alwaysAllowedRoutes, @@ -400,7 +400,7 @@ describe('Realtime', () => { app = await getSingleton(); port = process.env.PEPYATKA_SERVER_PORT || app.context.config.port; const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); luna = await createTestUser(); await goPrivate(luna); diff --git a/test/functional/archives.js b/test/functional/archives.js index 3cd1f6ab3..450d8d3d8 100644 --- a/test/functional/archives.js +++ b/test/functional/archives.js @@ -6,14 +6,14 @@ import expect from 'unexpected'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub, dbAdapter, Comment } from '../../app/models'; +import { pubSub, dbAdapter, Comment } from '../../app/models'; import * as testHelper from '../functional/functional_test_helper'; describe('Archives', () => { let app; before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/backlinks.js b/test/functional/backlinks.js index cca0eaf61..63e189a74 100644 --- a/test/functional/backlinks.js +++ b/test/functional/backlinks.js @@ -4,7 +4,7 @@ import expect from 'unexpected'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import { PubSubAdapter } from '../../app/support/PubSubAdapter'; import { @@ -87,7 +87,7 @@ describe('Backlinks in realtime', () => { const app = await getSingleton(); const port = process.env.PEPYATKA_SERVER_PORT || app.context.config.port; const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); [luna, mars] = await createTestUsers(['luna', 'mars']); lunaSession = await Session.create(port, 'Luna session'); diff --git a/test/functional/bookmarklet.js b/test/functional/bookmarklet.js index 5134df204..405e53449 100644 --- a/test/functional/bookmarklet.js +++ b/test/functional/bookmarklet.js @@ -9,7 +9,7 @@ import config from 'config'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; -import { dbAdapter, PubSub } from '../../app/models'; +import { dbAdapter, pubSub } from '../../app/models'; import { PubSubAdapter } from '../../app/support/PubSubAdapter'; import { @@ -30,7 +30,7 @@ describe('BookmarkletController', () => { const app = await getSingleton(); rtPort = process.env.PEPYATKA_SERVER_PORT || app.context.config.port; const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/comment_likes.js b/test/functional/comment_likes.js index 37f9fd44c..477838a26 100644 --- a/test/functional/comment_likes.js +++ b/test/functional/comment_likes.js @@ -8,7 +8,7 @@ import { v4 as uuidv4 } from 'uuid'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import { acceptRequestToJoinGroup, @@ -40,7 +40,7 @@ describe('Comment likes', () => { writeComment = createComment(); getPost = fetchPost(app); getFeed = fetchTimeline(app); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/comments.js b/test/functional/comments.js index 53bae4250..832a5d85e 100644 --- a/test/functional/comments.js +++ b/test/functional/comments.js @@ -6,7 +6,7 @@ import expect from 'unexpected'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { Comment, PubSub } from '../../app/models'; +import { Comment, pubSub } from '../../app/models'; import * as funcTestHelper from './functional_test_helper'; import { getCommentResponse } from './schemaV2-helper'; @@ -16,7 +16,7 @@ describe('CommentsController', () => { before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/events.js b/test/functional/events.js index 30784ab21..f3eb4fb93 100644 --- a/test/functional/events.js +++ b/test/functional/events.js @@ -4,7 +4,7 @@ import unexpected from 'unexpected'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; -import { PubSub, dbAdapter } from '../../app/models'; +import { pubSub, dbAdapter } from '../../app/models'; import { DummyPublisher } from '../../app/pubsub'; import { PubSubAdapter } from '../../app/support/PubSubAdapter'; import { EVENT_TYPES } from '../../app/support/EventTypes'; @@ -52,7 +52,7 @@ const expect = unexpected.clone().use(realtimeAssertions).use(schema.freefeedAss describe('EventService', () => { before(() => { - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); @@ -1784,7 +1784,7 @@ describe('EventService', () => { describe('EventsController', () => { before(() => { - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); @@ -2175,7 +2175,7 @@ describe('EventsController', () => { describe('Unread events counter', () => { before(() => { - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); let luna, mars, lunaUserModel; @@ -2287,7 +2287,7 @@ describe('Unread events counter realtime updates for ', () => { before(async () => { await getSingleton(); const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); }); let luna, mars; @@ -2799,7 +2799,7 @@ describe('Unread events counter realtime updates for ', () => { describe('eventById', () => { before(() => { - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); let luna, mars; diff --git a/test/functional/gone-users.js b/test/functional/gone-users.js index 0f5c852eb..173ae7e5e 100644 --- a/test/functional/gone-users.js +++ b/test/functional/gone-users.js @@ -6,7 +6,7 @@ import { simpleParser } from 'mailparser'; import { getSingleton } from '../../app/app'; import cleanDB from '../dbCleaner'; -import { dbAdapter, PubSub } from '../../app/models'; +import { dbAdapter, pubSub } from '../../app/models'; import { PubSubAdapter } from '../../app/support/PubSubAdapter'; import { GONE_SUSPENDED, GONE_COOLDOWN, GONE_DELETED } from '../../app/models/user'; import { addMailListener } from '../../lib/mailer'; @@ -42,7 +42,7 @@ describe('Gone users', () => { const app = await getSingleton(); port = process.env.PEPYATKA_SERVER_PORT || app.context.config.port; const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/group-moderation.js b/test/functional/group-moderation.js index 818768b5c..2e797ab90 100644 --- a/test/functional/group-moderation.js +++ b/test/functional/group-moderation.js @@ -6,7 +6,7 @@ import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { EVENT_TYPES } from '../../app/support/EventTypes'; import { PubSubAdapter } from '../../app/support/PubSubAdapter'; -import { dbAdapter, PubSub } from '../../app/models'; +import { dbAdapter, pubSub } from '../../app/models'; import { createTestUsers, @@ -468,7 +468,7 @@ describe('Group Moderation', () => { before(() => { port = process.env.PEPYATKA_SERVER_PORT || app.context.config.port; const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); }); let rtSession; diff --git a/test/functional/groups.js b/test/functional/groups.js index 77540d818..b1e7ca3a4 100644 --- a/test/functional/groups.js +++ b/test/functional/groups.js @@ -7,7 +7,7 @@ import config from 'config'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import * as funcTestHelper from './functional_test_helper'; @@ -16,7 +16,7 @@ describe('GroupsController', () => { before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/hidden-comments.js b/test/functional/hidden-comments.js index be34d693b..d31a4a0d5 100644 --- a/test/functional/hidden-comments.js +++ b/test/functional/hidden-comments.js @@ -4,7 +4,7 @@ import expect from 'unexpected'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; -import { PubSub, Comment } from '../../app/models'; +import { pubSub, Comment } from '../../app/models'; import { eventNames, PubSubAdapter } from '../../app/support/PubSubAdapter'; import { @@ -24,7 +24,7 @@ describe('Hidden comments', () => { before(async () => { app = await getSingleton(); const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/invitations.js b/test/functional/invitations.js index 297cb8db6..1adda213f 100644 --- a/test/functional/invitations.js +++ b/test/functional/invitations.js @@ -6,7 +6,7 @@ import unexpected from 'unexpected'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import { banUser, @@ -26,7 +26,7 @@ const expect = unexpected.clone().use(schema.freefeedAssertions); describe('Invitations', () => { before(async () => { await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/multi-homefeeds.js b/test/functional/multi-homefeeds.js index 13c685ea8..d4e4f5a7d 100644 --- a/test/functional/multi-homefeeds.js +++ b/test/functional/multi-homefeeds.js @@ -3,7 +3,7 @@ import expect from 'unexpected'; import cleanDB from '../dbCleaner'; -import { Timeline, PubSub, HOMEFEED_MODE_FRIENDS_ALL_ACTIVITY } from '../../app/models'; +import { Timeline, pubSub, HOMEFEED_MODE_FRIENDS_ALL_ACTIVITY } from '../../app/models'; import { getSingleton } from '../../app/app'; import { PubSubAdapter } from '../../app/support/PubSubAdapter'; @@ -32,7 +32,7 @@ describe(`Multiple home feeds API`, () => { const app = await getSingleton(); port = process.env.PEPYATKA_SERVER_PORT || app.context.config.port; const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); }); describe(`Home feeds management`, () => { diff --git a/test/functional/mutual_friends.js b/test/functional/mutual_friends.js index 3ce772e39..46053cd16 100644 --- a/test/functional/mutual_friends.js +++ b/test/functional/mutual_friends.js @@ -6,7 +6,7 @@ import expect from 'unexpected'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import * as funcTestHelper from './functional_test_helper'; @@ -15,7 +15,7 @@ describe('MutualFriends', () => { before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/passwords.js b/test/functional/passwords.js index 4bf019f64..22bf02223 100644 --- a/test/functional/passwords.js +++ b/test/functional/passwords.js @@ -7,7 +7,7 @@ import config from 'config'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { dbAdapter, PubSub } from '../../app/models'; +import { dbAdapter, pubSub } from '../../app/models'; import { addMailListener } from '../../lib/mailer'; import { createUserAsync, performJSONRequest, updateUserAsync } from './functional_test_helper'; @@ -15,7 +15,7 @@ import { createUserAsync, performJSONRequest, updateUserAsync } from './function describe('PasswordsController', () => { before(async () => { await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); describe('#create()', () => { diff --git a/test/functional/posts/leave-direct.js b/test/functional/posts/leave-direct.js index ccb4852bf..9194c1e21 100644 --- a/test/functional/posts/leave-direct.js +++ b/test/functional/posts/leave-direct.js @@ -5,7 +5,7 @@ import expect from 'unexpected'; import cleanDB from '../../dbCleaner'; import { getSingleton } from '../../../app/app'; import { PubSubAdapter } from '../../../app/support/PubSubAdapter'; -import { PubSub } from '../../../app/models'; +import { pubSub } from '../../../app/models'; import { authHeaders, createAndReturnPostToFeed, @@ -142,7 +142,7 @@ describe('POST /v2/posts/:postId/leave', () => { const app = await getSingleton(); port = process.env.PEPYATKA_SERVER_PORT || app.context.config.port; const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); }); let lunaSession, marsSession, venusSession; diff --git a/test/functional/posts/posts.js b/test/functional/posts/posts.js index 89fff1d01..294fbf9f6 100644 --- a/test/functional/posts/posts.js +++ b/test/functional/posts/posts.js @@ -8,7 +8,7 @@ import expect from 'unexpected'; import cleanDB from '../../dbCleaner'; import { getSingleton } from '../../../app/app'; import { DummyPublisher } from '../../../app/pubsub'; -import { PubSub } from '../../../app/models'; +import { pubSub } from '../../../app/models'; import * as funcTestHelper from '../functional_test_helper'; describe('PostsController', () => { @@ -16,7 +16,7 @@ describe('PostsController', () => { before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/posts/postsV2.js b/test/functional/posts/postsV2.js index c302f8cdd..699f12ac6 100644 --- a/test/functional/posts/postsV2.js +++ b/test/functional/posts/postsV2.js @@ -6,7 +6,7 @@ import expect from 'unexpected'; import cleanDB from '../../dbCleaner'; import { getSingleton } from '../../../app/app'; import { DummyPublisher } from '../../../app/pubsub'; -import { PubSub } from '../../../app/models'; +import { pubSub } from '../../../app/models'; import { createUserAsync, createAndReturnPost, @@ -36,7 +36,7 @@ describe('TimelinesControllerV2', () => { before(async () => { app = await getSingleton(); fetchPostOpenGraph = postOpenGraphFetcher(app); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/private_groups.js b/test/functional/private_groups.js index 87c7ef574..f9baef11e 100644 --- a/test/functional/private_groups.js +++ b/test/functional/private_groups.js @@ -5,7 +5,7 @@ import request from 'superagent'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import * as funcTestHelper from './functional_test_helper'; @@ -14,7 +14,7 @@ describe('PrivateGroups', () => { before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/privates.js b/test/functional/privates.js index fb76d49e6..00e30390b 100644 --- a/test/functional/privates.js +++ b/test/functional/privates.js @@ -7,7 +7,7 @@ import expect from 'unexpected'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import * as funcTestHelper from './functional_test_helper'; @@ -16,7 +16,7 @@ describe('Privates', () => { before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/realtime-nitifications.js b/test/functional/realtime-nitifications.js index 8c76b1106..941b1e447 100644 --- a/test/functional/realtime-nitifications.js +++ b/test/functional/realtime-nitifications.js @@ -5,7 +5,7 @@ import expect from 'unexpected'; import { eventNames, PubSubAdapter } from '../../app/support/PubSubAdapter'; import { getSingleton } from '../../app/app'; import cleanDB from '../dbCleaner'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import { EVENT_TYPES } from '../../app/support/EventTypes'; import { @@ -26,7 +26,7 @@ describe('Realtime Notifications', () => { const app = await getSingleton(); port = process.env.PEPYATKA_SERVER_PORT || app.context.config.port; const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); await cleanDB($pg_database); }); diff --git a/test/functional/realtime.js b/test/functional/realtime.js index 0589cbac9..2e1b0501c 100644 --- a/test/functional/realtime.js +++ b/test/functional/realtime.js @@ -4,7 +4,7 @@ import origExpect from 'unexpected'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; -import { dbAdapter, PubSub } from '../../app/models'; +import { dbAdapter, pubSub } from '../../app/models'; import { PubSubAdapter } from '../../app/support/PubSubAdapter'; import * as funcTestHelper from './functional_test_helper'; @@ -16,7 +16,7 @@ describe('Realtime (Socket.io)', () => { before(async () => { await getSingleton(); const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); }); let lunaContext = {}; diff --git a/test/functional/realtime2.js b/test/functional/realtime2.js index d290a3b77..14c09640c 100644 --- a/test/functional/realtime2.js +++ b/test/functional/realtime2.js @@ -6,7 +6,7 @@ import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { dbAdapter, - PubSub, + pubSub, HOMEFEED_MODE_FRIENDS_ONLY, HOMEFEED_MODE_CLASSIC, HOMEFEED_MODE_FRIENDS_ALL_ACTIVITY, @@ -24,7 +24,7 @@ describe('Realtime #2', () => { const app = await getSingleton(); port = process.env.PEPYATKA_SERVER_PORT || app.context.config.port; const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); }); let luna, mars, lunaSession, marsSession, anonSession; @@ -694,7 +694,7 @@ describe('Realtime: Homefeed modes', () => { const app = await getSingleton(); port = process.env.PEPYATKA_SERVER_PORT || app.context.config.port; const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); [luna, mars, venus] = await Promise.all([ funcTestHelper.createUserAsync('luna', 'pw'), @@ -882,7 +882,7 @@ describe('Realtime: Group time updates', () => { const app = await getSingleton(); const port = process.env.PEPYATKA_SERVER_PORT || app.context.config.port; const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); // luna, mars, venus, jupiter are users [luna, mars, venus, jupiter] = await funcTestHelper.createTestUsers([ diff --git a/test/functional/request_revocation.js b/test/functional/request_revocation.js index ec79889c3..db87308b1 100644 --- a/test/functional/request_revocation.js +++ b/test/functional/request_revocation.js @@ -5,7 +5,7 @@ import request from 'superagent'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import * as funcTestHelper from './functional_test_helper'; @@ -14,7 +14,7 @@ describe('RequestRevocation', () => { before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/search.js b/test/functional/search.js index 2beb6da0d..752092f55 100644 --- a/test/functional/search.js +++ b/test/functional/search.js @@ -6,14 +6,14 @@ import expect from 'unexpected'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import * as funcTestHelper from './functional_test_helper'; describe('SearchController', () => { before(async () => { await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); await cleanDB($pg_database); }); diff --git a/test/functional/session.js b/test/functional/session.js index 80b602d56..90ac87e48 100644 --- a/test/functional/session.js +++ b/test/functional/session.js @@ -11,7 +11,7 @@ import { getSingleton } from '../../app/app'; import { AuthToken, dbAdapter, - PubSub, + pubSub, SessionTokenV1, sessionTokenV1Store, User, @@ -29,7 +29,7 @@ describe('SessionController', () => { app = await getSingleton(); port = process.env.PEPYATKA_SERVER_PORT || app.context.config.port; const pubsubAdapter = new PubSubAdapter($database); - PubSub.setPublisher(pubsubAdapter); + pubSub.setPublisher(pubsubAdapter); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/summary.js b/test/functional/summary.js index 99220e836..1d3d985ce 100644 --- a/test/functional/summary.js +++ b/test/functional/summary.js @@ -3,14 +3,14 @@ import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import * as funcTestHelper from './functional_test_helper'; describe('SummaryController', () => { before(async () => { await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); describe('#generalSummary()', () => { diff --git a/test/functional/timeline.js b/test/functional/timeline.js index 502f5c963..d3d6d317e 100644 --- a/test/functional/timeline.js +++ b/test/functional/timeline.js @@ -5,7 +5,7 @@ import request from 'superagent'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub } from '../../app/models'; +import { pubSub } from '../../app/models'; import * as funcTestHelper from './functional_test_helper'; @@ -14,7 +14,7 @@ describe('TimelinesController', () => { before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/timelinesV2.js b/test/functional/timelinesV2.js index d8dfebbc2..b2b0b4570 100644 --- a/test/functional/timelinesV2.js +++ b/test/functional/timelinesV2.js @@ -8,7 +8,7 @@ import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; import { - PubSub, + pubSub, dbAdapter, HOMEFEED_MODE_CLASSIC, HOMEFEED_MODE_FRIENDS_ONLY, @@ -41,7 +41,7 @@ describe('TimelinesControllerV2', () => { before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/users.js b/test/functional/users.js index a99bb2b08..9676959b9 100755 --- a/test/functional/users.js +++ b/test/functional/users.js @@ -11,7 +11,7 @@ import config from 'config'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub, SessionTokenV1 } from '../../app/models'; +import { pubSub, SessionTokenV1 } from '../../app/models'; import * as funcTestHelper from './functional_test_helper'; import * as schema from './schemaV2-helper'; @@ -21,7 +21,7 @@ describe('UsersController', () => { before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database)); diff --git a/test/functional/usersV2.js b/test/functional/usersV2.js index 9f081d034..11b62bab0 100644 --- a/test/functional/usersV2.js +++ b/test/functional/usersV2.js @@ -9,7 +9,7 @@ import { sortBy, uniq } from 'lodash'; import cleanDB from '../dbCleaner'; import { getSingleton } from '../../app/app'; import { DummyPublisher } from '../../app/pubsub'; -import { PubSub, Comment, dbAdapter } from '../../app/models'; +import { pubSub, Comment, dbAdapter } from '../../app/models'; import { createUserAsync, mutualSubscriptions, @@ -37,7 +37,7 @@ describe('UsersControllerV2', () => { before(async () => { app = await getSingleton(); - PubSub.setPublisher(new DummyPublisher()); + pubSub.setPublisher(new DummyPublisher()); }); beforeEach(() => cleanDB($pg_database));