Roadmap/#1379 admin ui doc level access (#1624)
* feat: adds document level access endpoints so admin ui can now accurately reflect document level access control * chore(docs): new doc access callout, updates useDocumentInfo props from change
This commit is contained in:
@@ -5,12 +5,13 @@ import qs from 'qs';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useConfig } from '../Config';
|
||||
import { PaginatedDocs } from '../../../../mongoose/types';
|
||||
import { ContextType, Props, Version } from './types';
|
||||
import { ContextType, DocumentPermissions, EntityType, Props, Version } from './types';
|
||||
import { TypeWithID } from '../../../../globals/config/types';
|
||||
import { TypeWithTimestamps } from '../../../../collections/config/types';
|
||||
import { Where } from '../../../../types';
|
||||
import { DocumentPreferences } from '../../../../preferences/types';
|
||||
import { usePreferences } from '../Preferences';
|
||||
import { useAuth } from '../Auth';
|
||||
|
||||
const Context = createContext({} as ContextType);
|
||||
|
||||
@@ -23,24 +24,29 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
const { serverURL, routes: { api } } = useConfig();
|
||||
const { getPreference } = usePreferences();
|
||||
const { i18n } = useTranslation();
|
||||
const { permissions } = useAuth();
|
||||
const [publishedDoc, setPublishedDoc] = useState<TypeWithID & TypeWithTimestamps>(null);
|
||||
const [versions, setVersions] = useState<PaginatedDocs<Version>>(null);
|
||||
const [unpublishedVersions, setUnpublishedVersions] = useState<PaginatedDocs<Version>>(null);
|
||||
const [docPermissions, setDocPermissions] = useState<DocumentPermissions>(null);
|
||||
|
||||
const baseURL = `${serverURL}${api}`;
|
||||
let slug;
|
||||
let type;
|
||||
let preferencesKey;
|
||||
let slug: string;
|
||||
let type: EntityType;
|
||||
let pluralType: 'globals' | 'collections';
|
||||
let preferencesKey: string;
|
||||
|
||||
if (global) {
|
||||
slug = global.slug;
|
||||
type = 'global';
|
||||
pluralType = 'globals';
|
||||
preferencesKey = `global-${slug}`;
|
||||
}
|
||||
|
||||
if (collection) {
|
||||
slug = collection.slug;
|
||||
type = 'collection';
|
||||
pluralType = 'collections';
|
||||
|
||||
if (id) {
|
||||
preferencesKey = `collection-${slug}-${id}`;
|
||||
@@ -169,6 +175,25 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
}
|
||||
}, [i18n, global, collection, id, baseURL]);
|
||||
|
||||
const getDocPermissions = React.useCallback(async () => {
|
||||
let docAccessURL: string;
|
||||
if (pluralType === 'globals') {
|
||||
docAccessURL = `/globals/${slug}/access`;
|
||||
} else if (pluralType === 'collections' && id) {
|
||||
docAccessURL = `/${slug}/access/${id}`;
|
||||
}
|
||||
|
||||
if (docAccessURL) {
|
||||
const res = await fetch(`${serverURL}${api}${docAccessURL}`);
|
||||
const json = await res.json();
|
||||
setDocPermissions(json);
|
||||
} else {
|
||||
// fallback to permissions from the collection
|
||||
// (i.e. create has no id)
|
||||
setDocPermissions(permissions[pluralType][slug]);
|
||||
}
|
||||
}, [serverURL, api, pluralType, slug, id, permissions]);
|
||||
|
||||
useEffect(() => {
|
||||
getVersions();
|
||||
}, [getVersions]);
|
||||
@@ -181,6 +206,10 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
getDocPreferences();
|
||||
}, [getPreference, preferencesKey]);
|
||||
|
||||
useEffect(() => {
|
||||
getDocPermissions();
|
||||
}, [getDocPermissions]);
|
||||
|
||||
const value = {
|
||||
slug,
|
||||
type,
|
||||
@@ -192,6 +221,8 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
getVersions,
|
||||
publishedDoc,
|
||||
id,
|
||||
getDocPermissions,
|
||||
docPermissions,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -202,5 +233,3 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
};
|
||||
|
||||
export const useDocumentInfo = (): ContextType => useContext(Context);
|
||||
|
||||
export default Context;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { CollectionPermission, GlobalPermission } from '../../../../auth';
|
||||
import { SanitizedCollectionConfig, TypeWithID, TypeWithTimestamps } from '../../../../collections/config/types';
|
||||
import { SanitizedGlobalConfig } from '../../../../globals/config/types';
|
||||
import { PaginatedDocs } from '../../../../mongoose/types';
|
||||
@@ -6,10 +7,14 @@ import { TypeWithVersion } from '../../../../versions/types';
|
||||
|
||||
export type Version = TypeWithVersion<any>
|
||||
|
||||
export type DocumentPermissions = null | GlobalPermission | CollectionPermission
|
||||
|
||||
export type EntityType = 'global' | 'collection'
|
||||
|
||||
export type ContextType = {
|
||||
collection?: SanitizedCollectionConfig
|
||||
global?: SanitizedGlobalConfig
|
||||
type: 'global' | 'collection'
|
||||
type: EntityType
|
||||
/** Slug of the collection or global */
|
||||
slug?: string
|
||||
id?: string | number
|
||||
@@ -18,6 +23,8 @@ export type ContextType = {
|
||||
unpublishedVersions?: PaginatedDocs<Version>
|
||||
publishedDoc?: TypeWithID & TypeWithTimestamps & { _status?: string }
|
||||
getVersions: () => Promise<void>
|
||||
docPermissions: DocumentPermissions
|
||||
getDocPermissions: () => Promise<void>
|
||||
}
|
||||
|
||||
export type Props = {
|
||||
|
||||
@@ -20,9 +20,9 @@ const GlobalView: React.FC<IndexProps> = (props) => {
|
||||
const { state: locationState } = useLocation<{data?: Record<string, unknown>}>();
|
||||
const locale = useLocale();
|
||||
const { setStepNav } = useStepNav();
|
||||
const { permissions, user } = useAuth();
|
||||
const { user } = useAuth();
|
||||
const [initialState, setInitialState] = useState<Fields>();
|
||||
const { getVersions, preferencesKey } = useDocumentInfo();
|
||||
const { getVersions, preferencesKey, docPermissions, getDocPermissions } = useDocumentInfo();
|
||||
const { getPreference } = usePreferences();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -50,9 +50,10 @@ const GlobalView: React.FC<IndexProps> = (props) => {
|
||||
|
||||
const onSave = useCallback(async (json) => {
|
||||
getVersions();
|
||||
getDocPermissions();
|
||||
const state = await buildStateFromSchema({ fieldSchema: fields, data: json.result, operation: 'update', user, locale, t });
|
||||
setInitialState(state);
|
||||
}, [getVersions, fields, user, locale, t]);
|
||||
}, [getVersions, fields, user, locale, t, getDocPermissions]);
|
||||
|
||||
const [{ data }] = usePayloadAPI(
|
||||
`${serverURL}${api}/globals/${slug}`,
|
||||
@@ -79,16 +80,14 @@ const GlobalView: React.FC<IndexProps> = (props) => {
|
||||
awaitInitialState();
|
||||
}, [dataToRender, fields, user, locale, getPreference, preferencesKey, t]);
|
||||
|
||||
const globalPermissions = permissions?.globals?.[slug];
|
||||
|
||||
return (
|
||||
<RenderCustomComponent
|
||||
DefaultComponent={DefaultGlobal}
|
||||
CustomComponent={CustomEdit}
|
||||
componentProps={{
|
||||
isLoading: !initialState,
|
||||
isLoading: !initialState || !docPermissions,
|
||||
data: dataToRender,
|
||||
permissions: globalPermissions,
|
||||
permissions: docPermissions,
|
||||
initialState,
|
||||
global,
|
||||
onSave,
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useDocumentInfo } from '../../../utilities/DocumentInfo';
|
||||
import { Fields } from '../../../forms/Form/types';
|
||||
import { usePreferences } from '../../../utilities/Preferences';
|
||||
import { EditDepthContext } from '../../../utilities/EditDepth';
|
||||
import { CollectionPermission } from '../../../../../auth';
|
||||
|
||||
const EditView: React.FC<IndexProps> = (props) => {
|
||||
const { collection: incomingCollection, isEditing } = props;
|
||||
@@ -40,20 +41,21 @@ const EditView: React.FC<IndexProps> = (props) => {
|
||||
const { state: locationState } = useLocation();
|
||||
const history = useHistory();
|
||||
const [initialState, setInitialState] = useState<Fields>();
|
||||
const { permissions, user } = useAuth();
|
||||
const { getVersions, preferencesKey } = useDocumentInfo();
|
||||
const { user } = useAuth();
|
||||
const { getVersions, preferencesKey, getDocPermissions, docPermissions } = useDocumentInfo();
|
||||
const { getPreference } = usePreferences();
|
||||
const { t } = useTranslation('general');
|
||||
|
||||
const onSave = useCallback(async (json: any) => {
|
||||
getVersions();
|
||||
getDocPermissions();
|
||||
if (!isEditing) {
|
||||
setRedirect(`${admin}/collections/${collection.slug}/${json?.doc?.id}`);
|
||||
} else {
|
||||
const state = await buildStateFromSchema({ fieldSchema: collection.fields, data: json.doc, user, id, operation: 'update', locale, t });
|
||||
setInitialState(state);
|
||||
}
|
||||
}, [admin, collection, isEditing, getVersions, user, id, t, locale]);
|
||||
}, [admin, collection, isEditing, getVersions, user, id, t, locale, getDocPermissions]);
|
||||
|
||||
const [{ data, isLoading: isLoadingDocument, isError }] = usePayloadAPI(
|
||||
(isEditing ? `${serverURL}${api}/${slug}/${id}` : null),
|
||||
@@ -87,10 +89,9 @@ const EditView: React.FC<IndexProps> = (props) => {
|
||||
);
|
||||
}
|
||||
|
||||
const collectionPermissions = permissions?.collections?.[slug];
|
||||
const apiURL = `${serverURL}${api}/${slug}/${id}${collection.versions.drafts ? '?draft=true' : ''}`;
|
||||
const action = `${serverURL}${api}/${slug}${isEditing ? `/${id}` : ''}?locale=${locale}&depth=0&fallback-locale=null`;
|
||||
const hasSavePermission = (isEditing && collectionPermissions?.update?.permission) || (!isEditing && collectionPermissions?.create?.permission);
|
||||
const hasSavePermission = (isEditing && docPermissions?.update?.permission) || (!isEditing && (docPermissions as CollectionPermission)?.create?.permission);
|
||||
|
||||
return (
|
||||
<EditDepthContext.Provider value={1}>
|
||||
@@ -99,10 +100,10 @@ const EditView: React.FC<IndexProps> = (props) => {
|
||||
CustomComponent={CustomEdit}
|
||||
componentProps={{
|
||||
id,
|
||||
isLoading: !initialState,
|
||||
isLoading: !initialState || !docPermissions,
|
||||
data: dataToRender,
|
||||
collection,
|
||||
permissions: collectionPermissions,
|
||||
permissions: docPermissions,
|
||||
isEditing,
|
||||
onSave,
|
||||
initialState,
|
||||
|
||||
Reference in New Issue
Block a user