From 2b0fbee43ede8ccccb13b209c2fcd1027407ad5b Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Mon, 14 Oct 2024 10:59:49 +0300 Subject: [PATCH] chore(server): core IoC #40 - favoriteStreamFactory --- packages/frontend/.vscode/settings.json | 3 +- .../preview-service/.vscode/settings.json | 3 + .../modules/core/domain/streams/operations.ts | 18 +++ .../modules/core/graph/resolvers/streams.ts | 15 ++- .../modules/core/repositories/streams.ts | 113 +++++++++--------- .../server/modules/core/services/streams.js | 47 +------- .../modules/core/services/streams/favorite.ts | 40 ++++++- .../core/tests/discoverableStreams.spec.ts | 3 +- packages/ui-components/.vscode/settings.json | 3 + packages/viewer/.vscode/settings.json | 3 + 10 files changed, 139 insertions(+), 109 deletions(-) create mode 100644 packages/preview-service/.vscode/settings.json create mode 100644 packages/ui-components/.vscode/settings.json create mode 100644 packages/viewer/.vscode/settings.json diff --git a/packages/frontend/.vscode/settings.json b/packages/frontend/.vscode/settings.json index 7aa8a795bf..82356c9f1a 100644 --- a/packages/frontend/.vscode/settings.json +++ b/packages/frontend/.vscode/settings.json @@ -3,5 +3,6 @@ "typescript.suggest.autoImports": true, "typescript.preferences.importModuleSpecifier": "non-relative", "javascript.preferences.importModuleSpecifier": "non-relative", - "volar.completion.preferredTagNameCase": "kebab" + "volar.completion.preferredTagNameCase": "kebab", + "vitest.disableWorkspaceWarning": true } diff --git a/packages/preview-service/.vscode/settings.json b/packages/preview-service/.vscode/settings.json new file mode 100644 index 0000000000..bda259b1f7 --- /dev/null +++ b/packages/preview-service/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "vitest.disableWorkspaceWarning": true +} diff --git a/packages/server/modules/core/domain/streams/operations.ts b/packages/server/modules/core/domain/streams/operations.ts index 51372d5283..af76ea05b0 100644 --- a/packages/server/modules/core/domain/streams/operations.ts +++ b/packages/server/modules/core/domain/streams/operations.ts @@ -73,6 +73,17 @@ export type StoreStream = ( }> ) => Promise +export type SetStreamFavorited = (params: { + streamId: string + userId: string + favorited?: boolean +}) => Promise + +export type CanUserFavoriteStream = (params: { + userId: string + streamId: string +}) => Promise + export type DeleteStreamRecords = (streamId: string) => Promise export type GetOnboardingBaseStream = (version: string) => Promise> @@ -224,3 +235,10 @@ export type GetFavoriteStreamsCollection = (params: { cursor?: string | null | undefined streamIdWhitelist?: string[] | undefined }) => Promise<{ totalCount: number; cursor: Nullable; items: Stream[] }> + +export type FavoriteStream = (params: { + userId: string + streamId: string + favorited?: boolean | undefined + userResourceAccessRules?: ContextResourceAccessRules +}) => Promise diff --git a/packages/server/modules/core/graph/resolvers/streams.ts b/packages/server/modules/core/graph/resolvers/streams.ts index 8b1093858b..ec8ea5d6c9 100644 --- a/packages/server/modules/core/graph/resolvers/streams.ts +++ b/packages/server/modules/core/graph/resolvers/streams.ts @@ -1,6 +1,5 @@ import { getStreamUsers, - favoriteStream, getActiveUserStreamFavoriteDate, getStreamFavoritesCount, getOwnedFavoritesCount @@ -35,7 +34,9 @@ import { legacyGetStreamsFactory, getFavoritedStreamsCountFactory, getFavoritedStreamsPageFactory, - getStreamCollaboratorsFactory + getStreamCollaboratorsFactory, + canUserFavoriteStreamFactory, + setStreamFavoritedFactory } from '@/modules/core/repositories/streams' import { createStreamReturnRecordFactory, @@ -87,7 +88,10 @@ import { validateStreamAccessFactory } from '@/modules/core/services/streams/access' import { getDiscoverableStreamsFactory } from '@/modules/core/services/streams/discoverableStreams' -import { getFavoriteStreamsCollectionFactory } from '@/modules/core/services/streams/favorite' +import { + favoriteStreamFactory, + getFavoriteStreamsCollectionFactory +} from '@/modules/core/services/streams/favorite' const getFavoriteStreamsCollection = getFavoriteStreamsCollectionFactory({ getFavoritedStreamsCount: getFavoritedStreamsCountFactory({ db }), @@ -173,6 +177,11 @@ const getDiscoverableStreams = getDiscoverableStreamsFactory({ countDiscoverableStreams: countDiscoverableStreamsFactory({ db }) }) const getStreams = legacyGetStreamsFactory({ db }) +const favoriteStream = favoriteStreamFactory({ + canUserFavoriteStream: canUserFavoriteStreamFactory({ db }), + setStreamFavorited: setStreamFavoritedFactory({ db }), + getStream +}) const getUserStreamsCore = async ( forOtherUser: boolean, diff --git a/packages/server/modules/core/repositories/streams.ts b/packages/server/modules/core/repositories/streams.ts index b1af13c8e5..dfaedc7b77 100644 --- a/packages/server/modules/core/repositories/streams.ts +++ b/packages/server/modules/core/repositories/streams.ts @@ -86,7 +86,9 @@ import { GetDiscoverableStreamsPage, LegacyGetStreams, GetFavoritedStreamsPage, - GetFavoritedStreamsCount + GetFavoritedStreamsCount, + SetStreamFavorited, + CanUserFavoriteStream } from '@/modules/core/domain/streams/operations' export type { StreamWithOptionalRole, StreamWithCommitId } @@ -341,40 +343,39 @@ export const getFavoritedStreamsCountFactory = * @param {boolean} [p.favorited] By default favorites the stream, but you can set this * to false to unfavorite it */ -export async function setStreamFavorited(params: { - streamId: string - userId: string - favorited?: boolean -}) { - const { streamId, userId, favorited = true } = params +export const setStreamFavoritedFactory = + (deps: { db: Knex }): SetStreamFavorited => + async (params: { streamId: string; userId: string; favorited?: boolean }) => { + const { streamId, userId, favorited = true } = params + + if (!userId || !streamId) + throw new InvalidArgumentError('Invalid stream or user ID', { + info: { userId, streamId } + }) - if (!userId || !streamId) - throw new InvalidArgumentError('Invalid stream or user ID', { - info: { userId, streamId } + const favoriteQuery = tables.streamFavorites(deps.db).where({ + streamId, + userId }) - const favoriteQuery = StreamFavorites.knex().where({ - streamId, - userId - }) + if (!favorited) { + await favoriteQuery.del() + return + } + + // Upserting the favorite + await tables + .streamFavorites(deps.db) + .insert({ + userId, + streamId + }) + .onConflict(['streamId', 'userId']) + .ignore() - if (!favorited) { - await favoriteQuery.del() return } - // Upserting the favorite - await StreamFavorites.knex() - .insert({ - userId, - streamId - }) - .onConflict(['streamId', 'userId']) - .ignore() - - return -} - /** * Get favorite metadata for specified user and all specified stream IDs * @param {Object} p @@ -421,39 +422,35 @@ export async function getBatchStreamFavoritesCounts(streamIds: string[]) { /** * Check if user can favorite a stream - * @param {Object} p - * @param {string} userId - * @param {string} streamId - * @returns {Promise} */ -export async function canUserFavoriteStream(params: { - userId: string - streamId: string -}) { - const { userId, streamId } = params - - if (!userId || !streamId) - throw new InvalidArgumentError('Invalid stream or user ID', { - info: { userId, streamId } - }) +export const canUserFavoriteStreamFactory = + (deps: { db: Knex }): CanUserFavoriteStream => + async (params: { userId: string; streamId: string }) => { + const { userId, streamId } = params + + if (!userId || !streamId) + throw new InvalidArgumentError('Invalid stream or user ID', { + info: { userId, streamId } + }) - const query = Streams.knex() - .select>>([Streams.col.id]) - .leftJoin(StreamAcl.name, function () { - this.on(StreamAcl.col.resourceId, Streams.col.id).andOnVal( - StreamAcl.col.userId, - userId - ) - }) - .where(Streams.col.id, streamId) - .andWhere(function () { - this.where(Streams.col.isPublic, true).orWhereNotNull(StreamAcl.col.resourceId) - }) - .limit(1) + const query = tables + .streams(deps.db) + .select>>([Streams.col.id]) + .leftJoin(StreamAcl.name, function () { + this.on(StreamAcl.col.resourceId, Streams.col.id).andOnVal( + StreamAcl.col.userId, + userId + ) + }) + .where(Streams.col.id, streamId) + .andWhere(function () { + this.where(Streams.col.isPublic, true).orWhereNotNull(StreamAcl.col.resourceId) + }) + .limit(1) - const result = await query - return result?.length > 0 -} + const result = await query + return result?.length > 0 + } /** * Find total favorites of owned streams for specified users diff --git a/packages/server/modules/core/services/streams.js b/packages/server/modules/core/services/streams.js index 40ddc621bb..50902c1a66 100644 --- a/packages/server/modules/core/services/streams.js +++ b/packages/server/modules/core/services/streams.js @@ -1,14 +1,5 @@ -const { StreamAcl, knex } = require('@/modules/core/dbSchema') -const { - setStreamFavorited, - canUserFavoriteStream, - getStreamFactory -} = require('@/modules/core/repositories/streams') -const { UnauthorizedError, InvalidArgumentError } = require('@/modules/shared/errors') -const { isResourceAllowed } = require('@/modules/core/helpers/token') -const { - TokenResourceIdentifierType -} = require('@/modules/core/graph/generated/graphql') +const { StreamAcl } = require('@/modules/core/dbSchema') +const { InvalidArgumentError } = require('@/modules/shared/errors') /** * NOTE: Stop adding stuff to this service, create specialized service modules instead for various domains @@ -18,8 +9,6 @@ const { */ module.exports = { - setStreamFavorited, - /** * @returns {Promise<{role: string, id: string, name: string, company: string, avatar: string}[]>} */ @@ -35,38 +24,6 @@ module.exports = { return await query }, - /** - * Favorite or unfavorite a stream - * @param {Object} p - * @param {string} p.userId - * @param {string} p.streamId - * @param {boolean} [p.favorited] Whether to favorite or unfavorite (true by default) - * @param {import('@/modules/core/helpers/token').ContextResourceAccessRules} [p.userResourceAccessRules] Resource access rules (if any) for the user doing the favoriting - * @returns {Promise} Updated stream - */ - async favoriteStream({ userId, streamId, favorited, userResourceAccessRules }) { - // Check if user has access to stream - const canFavorite = await canUserFavoriteStream({ userId, streamId }) - const hasResourceAccess = isResourceAllowed({ - resourceId: streamId, - resourceAccessRules: userResourceAccessRules, - resourceType: TokenResourceIdentifierType.Project - }) - if (!canFavorite || !hasResourceAccess) { - throw new UnauthorizedError("User doesn't have access to the specified stream", { - info: { userId, streamId } - }) - } - - // Favorite/unfavorite the stream - await setStreamFavorited({ streamId, userId, favorited }) - - const getStream = getStreamFactory({ db: knex }) - - // Get updated stream info - return await getStream({ streamId, userId }) - }, - /** * Get active user stream favorite date (using dataloader) * @param {Object} p diff --git a/packages/server/modules/core/services/streams/favorite.ts b/packages/server/modules/core/services/streams/favorite.ts index 8d61d10637..07c37ba775 100644 --- a/packages/server/modules/core/services/streams/favorite.ts +++ b/packages/server/modules/core/services/streams/favorite.ts @@ -1,8 +1,15 @@ import { + CanUserFavoriteStream, + FavoriteStream, GetFavoritedStreamsCount, GetFavoritedStreamsPage, - GetFavoriteStreamsCollection + GetFavoriteStreamsCollection, + GetStream, + SetStreamFavorited } from '@/modules/core/domain/streams/operations' +import { TokenResourceIdentifierType } from '@/modules/core/domain/tokens/types' +import { isResourceAllowed } from '@/modules/core/helpers/token' +import { UnauthorizedError } from '@/modules/shared/errors' import { clamp } from 'lodash' /** @@ -35,3 +42,34 @@ export const getFavoriteStreamsCollectionFactory = return { totalCount, cursor: finalCursor, items: streams } } + +/** + * Favorite or unfavorite a stream + */ +export const favoriteStreamFactory = + (deps: { + canUserFavoriteStream: CanUserFavoriteStream + setStreamFavorited: SetStreamFavorited + getStream: GetStream + }): FavoriteStream => + async ({ userId, streamId, favorited, userResourceAccessRules }) => { + // Check if user has access to stream + const canFavorite = await deps.canUserFavoriteStream({ userId, streamId }) + const hasResourceAccess = isResourceAllowed({ + resourceId: streamId, + resourceAccessRules: userResourceAccessRules, + resourceType: TokenResourceIdentifierType.Project + }) + if (!canFavorite || !hasResourceAccess) { + throw new UnauthorizedError("User doesn't have access to the specified stream", { + info: { userId, streamId } + }) + } + + // Favorite/unfavorite the stream + await deps.setStreamFavorited({ streamId, userId, favorited }) + + // Get updated stream info + const stream = await deps.getStream({ streamId, userId }) + return stream! // It should exist, cause we already checked that it does + } diff --git a/packages/server/modules/core/tests/discoverableStreams.spec.ts b/packages/server/modules/core/tests/discoverableStreams.spec.ts index 31ecee2fac..1aa03ef598 100644 --- a/packages/server/modules/core/tests/discoverableStreams.spec.ts +++ b/packages/server/modules/core/tests/discoverableStreams.spec.ts @@ -3,7 +3,7 @@ import { db } from '@/db/knex' import { Streams, Users } from '@/modules/core/dbSchema' import { getStreamFactory, - setStreamFavorited + setStreamFavoritedFactory } from '@/modules/core/repositories/streams' import { Nullable, Optional } from '@/modules/shared/helpers/typeHelper' import { BasicTestUser, createTestUsers } from '@/test/authHelper' @@ -32,6 +32,7 @@ const READABLE_DISCOVERABLE_STREAM_COUNT = 15 const cleanup = async () => await truncateTables([Streams.name, Users.name]) const getStream = getStreamFactory({ db }) +const setStreamFavorited = setStreamFavoritedFactory({ db }) describe('Discoverable streams', () => { let apollo: ServerAndContext diff --git a/packages/ui-components/.vscode/settings.json b/packages/ui-components/.vscode/settings.json new file mode 100644 index 0000000000..bda259b1f7 --- /dev/null +++ b/packages/ui-components/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "vitest.disableWorkspaceWarning": true +} diff --git a/packages/viewer/.vscode/settings.json b/packages/viewer/.vscode/settings.json new file mode 100644 index 0000000000..bda259b1f7 --- /dev/null +++ b/packages/viewer/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "vitest.disableWorkspaceWarning": true +}