chore(server): core IoC #40 - favoriteStreamFactory (#3252)

This commit is contained in:
Kristaps Fabians Geikins
2024-10-14 09:44:45 +01:00
committed by GitHub
parent 6e6d1a00b5
commit adc7abe63d
10 changed files with 138 additions and 108 deletions
+2 -1
View File
@@ -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
}
+3
View File
@@ -0,0 +1,3 @@
{
"vitest.disableWorkspaceWarning": true
}
@@ -73,6 +73,17 @@ export type StoreStream = (
}>
) => Promise<Stream>
export type SetStreamFavorited = (params: {
streamId: string
userId: string
favorited?: boolean
}) => Promise<void>
export type CanUserFavoriteStream = (params: {
userId: string
streamId: string
}) => Promise<boolean>
export type DeleteStreamRecords = (streamId: string) => Promise<number>
export type GetOnboardingBaseStream = (version: string) => Promise<Optional<Stream>>
@@ -224,3 +235,10 @@ export type GetFavoriteStreamsCollection = (params: {
cursor?: string | null | undefined
streamIdWhitelist?: string[] | undefined
}) => Promise<{ totalCount: number; cursor: Nullable<string>; items: Stream[] }>
export type FavoriteStream = (params: {
userId: string
streamId: string
favorited?: boolean | undefined
userResourceAccessRules?: ContextResourceAccessRules
}) => Promise<Stream>
@@ -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,
@@ -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<boolean>}
*/
export async function canUserFavoriteStream(params: {
userId: string
streamId: string
}) {
const { userId, streamId } = params
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 }
})
if (!userId || !streamId)
throw new InvalidArgumentError('Invalid stream or user ID', {
info: { userId, streamId }
})
const query = Streams.knex()
.select<Array<Pick<StreamRecord, 'id'>>>([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<Array<Pick<StreamRecord, 'id'>>>([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
@@ -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<import('@/modules/core/helpers/types').StreamRecord>} 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
@@ -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
}
@@ -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
+3
View File
@@ -0,0 +1,3 @@
{
"vitest.disableWorkspaceWarning": true
}
+3
View File
@@ -0,0 +1,3 @@
{
"vitest.disableWorkspaceWarning": true
}