fix(ui): cannot replace the file if the user does not have delete access (#13484)
Currently, if you don't have delete access to the document, the UI doesn't allow you to replace the file, which isn't expected. This is also a UI only restriction, and the API allows you do this fine. This PR makes so the "remove file" button renders even if you don't have delete access, while still ensures you have update access. --------- Co-authored-by: Paul Popus <paul@payloadcms.com>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -331,5 +331,7 @@ test/databaseAdapter.js
|
||||
test/.localstack
|
||||
test/google-cloud-storage
|
||||
test/azurestoragedata/
|
||||
/media-without-delete-access
|
||||
|
||||
|
||||
licenses.csv
|
||||
|
||||
@@ -339,8 +339,7 @@ export const Upload_v4: React.FC<UploadProps_v4> = (props) => {
|
||||
}
|
||||
}, [isFormSubmitting])
|
||||
|
||||
const canRemoveUpload =
|
||||
docPermissions?.update && 'delete' in docPermissions && docPermissions?.delete
|
||||
const canRemoveUpload = docPermissions?.update
|
||||
|
||||
const hasImageSizes = uploadConfig?.imageSizes?.length > 0
|
||||
const hasResizeOptions = Boolean(uploadConfig?.resizeOptions)
|
||||
|
||||
1
test/uploads/.gitignore
vendored
1
test/uploads/.gitignore
vendored
@@ -10,3 +10,4 @@ without-meta-data
|
||||
svg-only
|
||||
/media-gif
|
||||
/custom-file-name-media
|
||||
/media-without-delete-access
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
listViewPreviewSlug,
|
||||
mediaSlug,
|
||||
mediaWithoutCacheTagsSlug,
|
||||
mediaWithoutDeleteAccessSlug,
|
||||
mediaWithoutRelationPreviewSlug,
|
||||
mediaWithRelationPreviewSlug,
|
||||
noRestrictFileMimeTypesSlug,
|
||||
@@ -931,6 +932,12 @@ export default buildConfigWithDefaults({
|
||||
staticDir: path.resolve(dirname, './svg-only'),
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: mediaWithoutDeleteAccessSlug,
|
||||
fields: [],
|
||||
upload: true,
|
||||
access: { delete: () => false },
|
||||
},
|
||||
],
|
||||
onInit: async (payload) => {
|
||||
const uploadsDir = path.resolve(dirname, './media')
|
||||
@@ -954,6 +961,8 @@ export default buildConfigWithDefaults({
|
||||
file: imageFile,
|
||||
})
|
||||
|
||||
await payload.create({ collection: mediaWithoutDeleteAccessSlug, data: {}, file: imageFile })
|
||||
|
||||
const { id: versionedImage } = await payload.create({
|
||||
collection: versionSlug,
|
||||
data: {
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
listViewPreviewSlug,
|
||||
mediaSlug,
|
||||
mediaWithoutCacheTagsSlug,
|
||||
mediaWithoutDeleteAccessSlug,
|
||||
relationPreviewSlug,
|
||||
relationSlug,
|
||||
svgOnlySlug,
|
||||
@@ -89,6 +90,7 @@ let stopCollectingErrorsFromPage: () => boolean
|
||||
let bulkUploadsURL: AdminUrlUtil
|
||||
let fileMimeTypeURL: AdminUrlUtil
|
||||
let svgOnlyURL: AdminUrlUtil
|
||||
let mediaWithoutDeleteAccessURL: AdminUrlUtil
|
||||
|
||||
describe('Uploads', () => {
|
||||
let page: Page
|
||||
@@ -129,6 +131,7 @@ describe('Uploads', () => {
|
||||
bulkUploadsURL = new AdminUrlUtil(serverURL, bulkUploadsSlug)
|
||||
fileMimeTypeURL = new AdminUrlUtil(serverURL, fileMimeTypeSlug)
|
||||
svgOnlyURL = new AdminUrlUtil(serverURL, svgOnlySlug)
|
||||
mediaWithoutDeleteAccessURL = new AdminUrlUtil(serverURL, mediaWithoutDeleteAccessSlug)
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
@@ -1599,4 +1602,22 @@ describe('Uploads', () => {
|
||||
|
||||
await saveDocAndAssert(page, '#action-save', 'error')
|
||||
})
|
||||
|
||||
test('should be able to replace the file even if the user doesnt have delete access', async () => {
|
||||
const docID = (await payload.find({ collection: mediaWithoutDeleteAccessSlug, limit: 1 }))
|
||||
.docs[0]?.id as string
|
||||
await page.goto(mediaWithoutDeleteAccessURL.edit(docID))
|
||||
const removeButton = page.locator('.file-details__remove')
|
||||
await expect(removeButton).toBeVisible()
|
||||
await removeButton.click()
|
||||
await expect(page.locator('input[type="file"]')).toBeAttached()
|
||||
await page.setInputFiles('input[type="file"]', path.join(dirname, 'test-image.jpg'))
|
||||
const filename = page.locator('.file-field__filename')
|
||||
await expect(filename).toHaveValue('test-image.jpg')
|
||||
await saveDocAndAssert(page)
|
||||
const filenameFromAPI = (
|
||||
await payload.find({ collection: mediaWithoutDeleteAccessSlug, limit: 1 })
|
||||
).docs[0]?.filename
|
||||
expect(filenameFromAPI).toBe('test-image.jpg')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -98,6 +98,7 @@ export interface Config {
|
||||
'externally-served-media': ExternallyServedMedia;
|
||||
'uploads-1': Uploads1;
|
||||
'uploads-2': Uploads2;
|
||||
'any-images': AnyImage;
|
||||
'admin-thumbnail-function': AdminThumbnailFunction;
|
||||
'admin-thumbnail-with-search-queries': AdminThumbnailWithSearchQuery;
|
||||
'admin-thumbnail-size': AdminThumbnailSize;
|
||||
@@ -119,6 +120,7 @@ export interface Config {
|
||||
'simple-relationship': SimpleRelationship;
|
||||
'file-mime-type': FileMimeType;
|
||||
'svg-only': SvgOnly;
|
||||
'media-without-delete-access': MediaWithoutDeleteAccess;
|
||||
users: User;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
@@ -157,6 +159,7 @@ export interface Config {
|
||||
'externally-served-media': ExternallyServedMediaSelect<false> | ExternallyServedMediaSelect<true>;
|
||||
'uploads-1': Uploads1Select<false> | Uploads1Select<true>;
|
||||
'uploads-2': Uploads2Select<false> | Uploads2Select<true>;
|
||||
'any-images': AnyImagesSelect<false> | AnyImagesSelect<true>;
|
||||
'admin-thumbnail-function': AdminThumbnailFunctionSelect<false> | AdminThumbnailFunctionSelect<true>;
|
||||
'admin-thumbnail-with-search-queries': AdminThumbnailWithSearchQueriesSelect<false> | AdminThumbnailWithSearchQueriesSelect<true>;
|
||||
'admin-thumbnail-size': AdminThumbnailSizeSelect<false> | AdminThumbnailSizeSelect<true>;
|
||||
@@ -178,6 +181,7 @@ export interface Config {
|
||||
'simple-relationship': SimpleRelationshipSelect<false> | SimpleRelationshipSelect<true>;
|
||||
'file-mime-type': FileMimeTypeSelect<false> | FileMimeTypeSelect<true>;
|
||||
'svg-only': SvgOnlySelect<false> | SvgOnlySelect<true>;
|
||||
'media-without-delete-access': MediaWithoutDeleteAccessSelect<false> | MediaWithoutDeleteAccessSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
@@ -1313,6 +1317,24 @@ export interface AdminThumbnailSize {
|
||||
};
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "any-images".
|
||||
*/
|
||||
export interface AnyImage {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
thumbnailURL?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "admin-thumbnail-function".
|
||||
@@ -1623,6 +1645,24 @@ export interface SvgOnly {
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media-without-delete-access".
|
||||
*/
|
||||
export interface MediaWithoutDeleteAccess {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
thumbnailURL?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
@@ -1778,6 +1818,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'uploads-2';
|
||||
value: string | Uploads2;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'any-images';
|
||||
value: string | AnyImage;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'admin-thumbnail-function';
|
||||
value: string | AdminThumbnailFunction;
|
||||
@@ -1862,6 +1906,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'svg-only';
|
||||
value: string | SvgOnly;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'media-without-delete-access';
|
||||
value: string | MediaWithoutDeleteAccess;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
@@ -3019,6 +3067,23 @@ export interface Uploads2Select<T extends boolean = true> {
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "any-images_select".
|
||||
*/
|
||||
export interface AnyImagesSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
url?: T;
|
||||
thumbnailURL?: T;
|
||||
filename?: T;
|
||||
mimeType?: T;
|
||||
filesize?: T;
|
||||
width?: T;
|
||||
height?: T;
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "admin-thumbnail-function_select".
|
||||
@@ -3386,6 +3451,23 @@ export interface SvgOnlySelect<T extends boolean = true> {
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media-without-delete-access_select".
|
||||
*/
|
||||
export interface MediaWithoutDeleteAccessSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
url?: T;
|
||||
thumbnailURL?: T;
|
||||
filename?: T;
|
||||
mimeType?: T;
|
||||
filesize?: T;
|
||||
width?: T;
|
||||
height?: T;
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
|
||||
@@ -40,3 +40,4 @@ export const bulkUploadsSlug = 'bulk-uploads'
|
||||
export const fileMimeTypeSlug = 'file-mime-type'
|
||||
export const svgOnlySlug = 'svg-only'
|
||||
export const anyImagesSlug = 'any-images'
|
||||
export const mediaWithoutDeleteAccessSlug = 'media-without-delete-access'
|
||||
|
||||
Reference in New Issue
Block a user