diff --git a/packages/server/modules/activitystream/domain/operations.ts b/packages/server/modules/activitystream/domain/operations.ts index f529a9c90..84e3869a5 100644 --- a/packages/server/modules/activitystream/domain/operations.ts +++ b/packages/server/modules/activitystream/domain/operations.ts @@ -114,3 +114,22 @@ export type GetResourceActivity = ({ cursor: string | null items: StreamActivityRecord[] }> + +export type GetUserActivity = ({ + userId, + actionType, + before, + after, + cursor, + limit +}: { + userId: string + actionType: StreamActionType + after?: Date + before?: Date + cursor?: Date + limit?: number +}) => Promise<{ + cursor: string | null + items: StreamActivityRecord[] +}> diff --git a/packages/server/modules/activitystream/graph/resolvers/activity.js b/packages/server/modules/activitystream/graph/resolvers/activity.js deleted file mode 100644 index 71081a0e9..000000000 --- a/packages/server/modules/activitystream/graph/resolvers/activity.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict' -const { md5 } = require('@/modules/shared/helpers/cryptoHelper') -const { getUserActivity } = require('../../services/index') -const { - getActivityCountByUserIdFactory, - getTimelineCountFactory, - getUserTimelineFactory -} = require('@/modules/activitystream/repositories') -const { db } = require('@/db/knex') - -const userActivityQueryCore = async (parent, args) => { - const { items, cursor } = await getUserActivity({ - userId: parent.id, - actionType: args.actionType, - after: args.after, - before: args.before, - cursor: args.cursor, - limit: args.limit - }) - const totalCount = await getActivityCountByUserIdFactory({ db })({ - userId: parent.id, - actionType: args.actionType, - after: args.after, - before: args.before - }) - - return { items, cursor, totalCount } -} - -const userTimelineQueryCore = async (parent, args) => { - const { items, cursor } = await getUserTimelineFactory({ db })({ - userId: parent.id, - after: args.after, - before: args.before, - cursor: args.cursor, - limit: args.limit - }) - const totalCount = await getTimelineCountFactory({ db })({ - userId: parent.id, - after: args.after, - before: args.before - }) - - return { items, cursor, totalCount } -} - -/** @type {import('@/modules/core/graph/generated/graphql').Resolvers} */ -module.exports = { - LimitedUser: { - async activity(parent, args) { - return await userActivityQueryCore(parent, args) - }, - - async timeline(parent, args) { - return await userTimelineQueryCore(parent, args) - } - }, - User: { - async activity(parent, args) { - return await userActivityQueryCore(parent, args) - }, - - async timeline(parent, args) { - return await userTimelineQueryCore(parent, args) - } - }, - Activity: { - /** - * We need a unique ID to be able to properly cache stuff on the clientside - */ - id(parent) { - if (!parent) return null - const { streamId, resourceId, userId, time } = parent - const plainIdentity = JSON.stringify({ - streamId, - resourceId, - userId, - time - }) - - return md5(plainIdentity) - } - } -} diff --git a/packages/server/modules/activitystream/graph/resolvers/activity-new.ts b/packages/server/modules/activitystream/graph/resolvers/activity.ts similarity index 54% rename from packages/server/modules/activitystream/graph/resolvers/activity-new.ts rename to packages/server/modules/activitystream/graph/resolvers/activity.ts index 172977ccb..b96a58020 100644 --- a/packages/server/modules/activitystream/graph/resolvers/activity-new.ts +++ b/packages/server/modules/activitystream/graph/resolvers/activity.ts @@ -3,14 +3,87 @@ import { ActionTypes } from '@/modules/activitystream/helpers/types' import { getActivityCountByResourceIdFactory, getActivityCountByStreamIdFactory, + getActivityCountByUserIdFactory, getResourceActivityFactory, - getStreamActivityFactory + getStreamActivityFactory, + getTimelineCountFactory, + getUserActivityFactory, + getUserTimelineFactory } from '@/modules/activitystream/repositories' import { Resolvers } from '@/modules/core/graph/generated/graphql' import { InvalidActionTypeError } from '@/modules/activitystream/errors/activityStream' import { StreamActionType } from '@/modules/activitystream/domain/types' +import { md5 } from '@/modules/shared/helpers/cryptoHelper' + +type ActivityPaginatedArgs = { + actionType?: string | null + after?: Date | null + before?: Date | null + cursor?: Date | null + limit?: number // This field is required because the type-defs defines it as required +} + +const userActivityQueryCore = async ( + parent: { id: string }, + args: ActivityPaginatedArgs +) => { + const { items, cursor } = await getUserActivityFactory({ db })({ + userId: parent.id, + actionType: (args.actionType as StreamActionType) ?? undefined, + after: args.after ?? undefined, + before: args.before ?? undefined, + cursor: args.cursor ?? undefined, + limit: args.limit + }) + const totalCount = await getActivityCountByUserIdFactory({ db })({ + userId: parent.id, + actionType: (args.actionType as StreamActionType) ?? undefined, + after: args.after ?? undefined, + before: args.before ?? undefined + }) + + return { items, cursor, totalCount } +} + +const userTimelineQueryCore = async ( + parent: { id: string }, + args: ActivityPaginatedArgs +) => { + const { items, cursor } = await getUserTimelineFactory({ db })({ + userId: parent.id, + after: args.after ?? undefined, + before: args.before ?? undefined, + cursor: args.cursor ?? undefined, + limit: args.limit + }) + const totalCount = await getTimelineCountFactory({ db })({ + userId: parent.id, + after: args.after ?? undefined, + before: args.before ?? undefined + }) + + return { items, cursor, totalCount } +} export = { + LimitedUser: { + async activity(parent, args) { + return await userActivityQueryCore(parent, args) + }, + + async timeline(parent, args) { + return await userTimelineQueryCore(parent, args) + } + }, + User: { + async activity(parent, args) { + return await userActivityQueryCore(parent, args) + }, + + async timeline(parent, args) { + return await userTimelineQueryCore(parent, args) + } + }, Stream: { async activity(parent, args) { if ( @@ -79,5 +152,22 @@ export = { return { items, cursor, totalCount } } + }, + Activity: { + /** + * We need a unique ID to be able to properly cache stuff on the clientside + */ + id(parent) { + if (!parent) return null + const { streamId, resourceId, userId, time } = parent + const plainIdentity = JSON.stringify({ + streamId, + resourceId, + userId, + time + }) + + return md5(plainIdentity) + } } } as Resolvers diff --git a/packages/server/modules/activitystream/repositories/index.ts b/packages/server/modules/activitystream/repositories/index.ts index ee9232dc3..ea51018d0 100644 --- a/packages/server/modules/activitystream/repositories/index.ts +++ b/packages/server/modules/activitystream/repositories/index.ts @@ -7,6 +7,7 @@ import { GetResourceActivity, GetStreamActivity, GetTimelineCount, + GetUserActivity, GetUserTimeline } from '@/modules/activitystream/domain/operations' import { @@ -191,3 +192,24 @@ export const getResourceActivityFactory = cursor: results.length > 0 ? results[results.length - 1].time.toISOString() : null } } + +export const getUserActivityFactory = + ({ db }: { db: Knex }): GetUserActivity => + async ({ userId, actionType, after, before, cursor, limit }) => { + if (!limit) { + limit = 200 + } + + const dbQuery = tables.streamActivity(db).where({ userId }) + if (actionType) dbQuery.andWhere({ actionType }) + if (after) dbQuery.andWhere('time', '>', after) + if (before) dbQuery.andWhere('time', '<', before) + if (cursor) dbQuery.andWhere('time', '<', cursor) + dbQuery.orderBy('time', 'desc').limit(limit) + + const results = await dbQuery.select('*') + return { + items: results, + cursor: results.length > 0 ? results[results.length - 1].time.toISOString() : null + } + } diff --git a/packages/server/modules/activitystream/services/index.js b/packages/server/modules/activitystream/services/index.js index 655c7e53b..4c4c87af5 100644 --- a/packages/server/modules/activitystream/services/index.js +++ b/packages/server/modules/activitystream/services/index.js @@ -61,24 +61,5 @@ module.exports = { { trx } ) } - }, - - async getUserActivity({ userId, actionType, after, before, cursor, limit }) { - if (!limit) { - limit = 200 - } - - const dbQuery = StreamActivity().where({ userId }) - if (actionType) dbQuery.andWhere({ actionType }) - if (after) dbQuery.andWhere('time', '>', after) - if (before) dbQuery.andWhere('time', '<', before) - if (cursor) dbQuery.andWhere('time', '<', cursor) - dbQuery.orderBy('time', 'desc').limit(limit) - - const results = await dbQuery.select('*') - return { - items: results, - cursor: results.length > 0 ? results[results.length - 1].time.toISOString() : null - } } } diff --git a/packages/server/modules/activitystream/tests/activity.spec.js b/packages/server/modules/activitystream/tests/activity.spec.js index f56823862..d7760ad88 100644 --- a/packages/server/modules/activitystream/tests/activity.spec.js +++ b/packages/server/modules/activitystream/tests/activity.spec.js @@ -4,7 +4,6 @@ const expect = require('chai').expect const { createUser } = require('../../core/services/users') const { createPersonalAccessToken } = require('../../core/services/tokens') const { createObject } = require('../../core/services/objects') -const { getUserActivity } = require('../services') const { beforeEachContext, initializeTestServer } = require('@/test/hooks') const { noErrors } = require('@/test/helpers') @@ -12,6 +11,10 @@ const { addOrUpdateStreamCollaborator } = require('@/modules/core/services/streams/streamAccessService') const { Roles, Scopes } = require('@speckle/shared') +const { getUserActivityFactory } = require('@/modules/activitystream/repositories') +const { db } = require('@/db/knex') + +const getUserActivity = getUserActivityFactory({ db }) let sendRequest