committed by
GitHub
parent
6e6d1a00b5
commit
adc7abe63d
+2
-1
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"vitest.disableWorkspaceWarning": true
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"vitest.disableWorkspaceWarning": true
|
||||
}
|
||||
Reference in New Issue
Block a user