sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
167 lines (148 loc) • 5.24 kB
text/typescript
import {type InitialValueResolverContext, type Schema} from '@sanity/types'
import {combineLatest, from, type Observable, of} from 'rxjs'
import {map, switchMap} from 'rxjs/operators'
import {useSchema, useTemplates} from '../../../hooks'
import {type InitialValueTemplateItem, resolveInitialValue, type Template} from '../../../templates'
import {
createHookFromObservableFactory,
getDraftId,
getPublishedId,
type PartialExcept,
} from '../../../util'
import {useGrantsStore} from '../datastores'
import {useInitialValueResolverContext} from '../document'
import {getDocumentValuePermissions} from './documentValuePermissions'
import {type GrantsStore, type PermissionCheckResult} from './types'
/** @internal */
export interface TemplatePermissionsResult<TInitialValue = Record<string, unknown>>
extends PermissionCheckResult,
InitialValueTemplateItem {
granted: boolean
reason: string
resolvedInitialValue: TInitialValue
subtitle?: string
template: Template
}
type Serializable<T> = {serialize(): T}
function serialize<T>(item: T | Serializable<T>): T {
if (typeof item === 'object' && item !== null && 'serialize' in item) {
return serialize(item.serialize())
}
return item as T
}
/** @internal */
export interface TemplatePermissionsOptions {
grantsStore: GrantsStore
schema: Schema
templates: Template[]
templateItems: InitialValueTemplateItem[]
context: InitialValueResolverContext
}
/**
* The observable version of `useTemplatePermissions`
*
* @internal
*/
export function getTemplatePermissions({
grantsStore,
templateItems,
templates,
schema,
context,
}: TemplatePermissionsOptions): Observable<
Array<TemplatePermissionsResult<Record<string, unknown>>>
> {
if (!templateItems?.length) return of([])
return combineLatest(
templateItems
.map(serialize)
.map(async (item) => {
const template = templates.find((t) => t.id === item.templateId)
if (!template) {
throw new Error(`template not found: "${item.templateId}"`)
}
const resolvedInitialValue = await resolveInitialValue(
schema,
template,
item.parameters,
context,
)
return {template, item, resolvedInitialValue}
})
.map((promise) =>
from(promise).pipe(
switchMap(({item, resolvedInitialValue, template}) => {
const schemaType = schema.get(template.schemaType)
if (!schemaType) {
throw new Error(`schema type not found: "${template.schemaType}"`)
}
const liveEdit = schemaType?.liveEdit
const {initialDocumentId = 'dummy-id'} = item
return getDocumentValuePermissions({
grantsStore,
permission: 'create',
document: {
_id: liveEdit ? getPublishedId(initialDocumentId) : getDraftId(initialDocumentId),
...resolvedInitialValue,
},
}).pipe(
map(({granted, reason}) => {
const title = item.title || template.title
const result: TemplatePermissionsResult = {
...item,
i18n: item.i18n || template.i18n,
granted,
reason,
resolvedInitialValue,
template,
title,
subtitle: schemaType.title === title ? undefined : schemaType.title,
description: item.description || template.description,
icon: item.icon || template.icon,
}
return result
}),
)
}),
),
),
)
}
/**
* Takes in an array of initial template values and returns an object of
* `TemplatePermissionsResult` keyed by the IDs of the initial template values
* given.
*
* The `TemplatePermissionsResult` is an object that contains a `granted`
* boolean per key and can be used to determine if a user has the ability to
* create documents using the given initial value template items.
*
* For each initial template value item, the corresponding template is found and
* resolved against the parameters in each the initial template value item. The
* resolved value is then run through the document-value permissions. If there
* are any matching grants for the resolved initial template value, the
* `TemplatePermissionsResult` will include `granted: true`.
*
* @internal
*/
export const useTemplatePermissionsFromHookFactory =
createHookFromObservableFactory(getTemplatePermissions)
/** @internal */
export function useTemplatePermissions({
templateItems,
...rest
}: PartialExcept<TemplatePermissionsOptions, 'templateItems'>): ReturnType<
typeof useTemplatePermissionsFromHookFactory
> {
const schema = useSchema()
const templates = useTemplates()
const grantsStore = useGrantsStore()
const initialValueContext = useInitialValueResolverContext()
return useTemplatePermissionsFromHookFactory({
templateItems,
grantsStore: rest.grantsStore || grantsStore,
schema: rest.schema || schema,
templates: rest.templates || templates,
context: initialValueContext,
})
}