Merge branch 'master' of github.com:payloadcms/payload

This commit is contained in:
James
2022-09-23 09:49:06 -07:00
38 changed files with 430 additions and 139 deletions

View File

@@ -32,9 +32,11 @@ const Duplicate: React.FC<Props> = ({ slug, collection, id }) => {
return;
}
const create = async (locale?: string): Promise<string | null> => {
const localeParam = locale ? `locale=${locale}` : '';
const response = await requests.get(`${serverURL}${api}/${slug}/${id}?${localeParam}`);
const create = async (locale = ''): Promise<string | null> => {
const response = await requests.get(`${serverURL}${api}/${slug}/${id}`, {
locale,
depth: 0,
});
const data = await response.json();
const result = await requests.post(`${serverURL}${api}/${slug}`, {
headers: {
@@ -59,7 +61,10 @@ const Duplicate: React.FC<Props> = ({ slug, collection, id }) => {
.filter((locale) => locale !== localization.defaultLocale)
.forEach(async (locale) => {
if (!abort) {
const res = await requests.get(`${serverURL}${api}/${slug}/${id}?locale=${locale}`);
const res = await requests.get(`${serverURL}${api}/${slug}/${id}`, {
locale,
depth: 0,
});
const localizedDoc = await res.json();
const patchResult = await requests.patch(`${serverURL}${api}/${slug}/${duplicateID}?locale=${locale}`, {
headers: {

View File

@@ -88,7 +88,7 @@ const RenderFields: React.FC<Props> = (props) => {
DefaultComponent={FieldComponent}
componentProps={{
...field,
path: field.path || (isFieldAffectingData ? field.name : undefined),
path: field.path || (isFieldAffectingData ? field.name : ''),
fieldTypes,
admin: {
...(field.admin || {}),

View File

@@ -6,7 +6,7 @@ export type Props = {
className?: string
readOnly?: boolean
forceRender?: boolean
permissions?: {
permissions?: FieldPermissions | {
[field: string]: FieldPermissions
}
filter?: (field: Field) => boolean

View File

@@ -75,7 +75,7 @@ const CollapsibleField: React.FC<Props> = (props) => {
<RenderFields
forceRender
readOnly={readOnly}
permissions={permissions?.fields}
permissions={permissions}
fieldTypes={fieldTypes}
fieldSchema={fields.map((field) => ({
...field,

View File

@@ -28,7 +28,7 @@ const Row: React.FC<Props> = (props) => {
<RenderFields
readOnly={readOnly}
className={classes}
permissions={permissions?.fields}
permissions={permissions}
fieldTypes={fieldTypes}
fieldSchema={fields.map((field) => ({
...field,

View File

@@ -71,7 +71,7 @@ const TabsField: React.FC<Props> = (props) => {
key={String(activeTab.label)}
forceRender
readOnly={readOnly}
permissions={permissions?.fields}
permissions={tabHasName(activeTab) ? permissions[activeTab.name].fields : permissions}
fieldTypes={fieldTypes}
fieldSchema={activeTab.fields.map((field) => ({
...field,

View File

@@ -28,7 +28,7 @@ const withCondition = <P extends Record<string, unknown>>(Field: React.Component
path?: string
};
const path = pathFromProps || name;
const path = typeof pathFromProps === 'string' ? pathFromProps : name;
const { getData, getSiblingData, getField, dispatchFields } = useWatchForm();

View File

@@ -82,7 +82,7 @@ const Dashboard: React.FC<Props> = (props) => {
if (type === EntityType.global) {
title = entity.label;
onClick = () => push({ pathname: `${admin}/globals/${global.slug}` });
onClick = () => push({ pathname: `${admin}/globals/${entity.slug}` });
}
return (

View File

@@ -1,6 +1,7 @@
import { PayloadRequest } from '../../express/types';
import { Permissions } from '../types';
import { adminInit as adminInitTelemetry } from '../../utilities/telemetry/events/adminInit';
import { tabHasName } from '../../fields/config/types';
const allOperations = ['create', 'read', 'update', 'delete'];
@@ -66,7 +67,12 @@ async function accessOperation(args: Arguments): Promise<Permissions> {
executeFieldPolicies(updatedObj, field.fields, operation);
} else if (field.type === 'tabs') {
field.tabs.forEach((tab) => {
executeFieldPolicies(updatedObj, tab.fields, operation);
if (tabHasName(tab)) {
if (!updatedObj[tab.name]) updatedObj[tab.name] = { fields: {} };
executeFieldPolicies(updatedObj[tab.name].fields, tab.fields, operation);
} else {
executeFieldPolicies(updatedObj, tab.fields, operation);
}
});
}
});

View File

@@ -226,7 +226,7 @@ export type CollectionConfig = {
/**
* Custom rest api endpoints
*/
endpoints?: Endpoint[]
endpoints?: Omit<Endpoint, 'root'>[]
/**
* Access control
*/

View File

@@ -119,7 +119,7 @@ function initCollectionsGraphQL(payload: Payload): void {
singularLabel,
);
if (collection.config.auth) {
if (collection.config.auth && !collection.config.auth.disableLocalStrategy) {
fields.push({
name: 'password',
label: 'Password',
@@ -274,21 +274,23 @@ function initCollectionsGraphQL(payload: Payload): void {
}
if (collection.config.auth) {
const authFields: Field[] = collection.config.auth.disableLocalStrategy ? [] : [{
name: 'email',
type: 'email',
required: true,
}]
collection.graphQL.JWT = buildObjectType({
payload,
name: formatName(`${slug}JWT`),
fields: collection.config.fields.filter((field) => fieldAffectsData(field) && field.saveToJWT).concat([
{
name: 'email',
type: 'email',
required: true,
},
fields: [
...collection.config.fields.filter((field) => fieldAffectsData(field) && field.saveToJWT),
...authFields,
{
name: 'collection',
type: 'text',
required: true,
},
]),
}
],
parentName: formatName(`${slug}JWT`),
});

View File

@@ -113,7 +113,7 @@ export default function registerCollections(ctx: Payload): void {
}
const endpoints = buildEndpoints(collection);
mountEndpoints(router, endpoints);
mountEndpoints(ctx.express, router, endpoints);
ctx.router.use(`/${slug}`, router);
}

View File

@@ -8,6 +8,7 @@ const component = joi.alternatives().try(
export const endpointsSchema = joi.array().items(joi.object({
path: joi.string(),
method: joi.string().valid('get', 'head', 'post', 'put', 'patch', 'delete', 'connect', 'options'),
root: joi.bool(),
handler: joi.alternatives().try(
joi.array().items(joi.func()),
joi.func(),

View File

@@ -79,16 +79,19 @@ export type AccessResult = boolean | Where;
*/
export type Access = (args?: any) => AccessResult | Promise<AccessResult>;
export interface PayloadHandler {(
export interface PayloadHandler {
(
req: PayloadRequest,
res: Response,
next: NextFunction,
): void }
): void
}
export type Endpoint = {
path: string
method: 'get' | 'head' | 'post' | 'put' | 'patch' | 'delete' | 'connect' | 'options' | string
handler: PayloadHandler | PayloadHandler[]
root?: boolean
}
export type AdminView = React.ComponentType<{ user: User, canAccessAdmin: boolean }>

View File

@@ -1,9 +1,13 @@
import { Router } from 'express';
import { Express, Router } from 'express';
import { Endpoint } from '../config/types';
function mountEndpoints(router: Router, endpoints: Endpoint[]): void {
function mountEndpoints(express: Express, router: Router, endpoints: Endpoint[]): void {
endpoints.forEach((endpoint) => {
router[endpoint.method](endpoint.path, endpoint.handler);
if (!endpoint.root) {
router[endpoint.method](endpoint.path, endpoint.handler);
} else {
express[endpoint.method](endpoint.path, endpoint.handler);
}
});
}

View File

@@ -56,7 +56,7 @@ export type GlobalConfig = {
beforeRead?: BeforeReadHook[]
afterRead?: AfterReadHook[]
}
endpoints?: Endpoint[],
endpoints?: Omit<Endpoint, 'root'>[],
access?: {
read?: Access;
readDrafts?: Access;

View File

@@ -48,7 +48,7 @@ export default function initGlobals(ctx: Payload): void {
const { slug } = global;
const endpoints = buildEndpoints(global);
mountEndpoints(router, endpoints);
mountEndpoints(ctx.express, router, endpoints);
ctx.router.use(`/globals/${slug}`, router);
});

View File

@@ -14,8 +14,8 @@ const buildPaginatedListType = (name, docType) => new GraphQLObjectType({
pagingCounter: { type: GraphQLInt },
hasPrevPage: { type: GraphQLBoolean },
hasNextPage: { type: GraphQLBoolean },
prevPage: { type: GraphQLBoolean },
nextPage: { type: GraphQLBoolean },
prevPage: { type: GraphQLInt },
nextPage: { type: GraphQLInt },
},
});

View File

@@ -106,7 +106,7 @@ export const init = (payload: Payload, options: InitOptions): void => {
initGraphQLPlayground(payload);
}
mountEndpoints(payload.router, payload.config.endpoints);
mountEndpoints(options.express, payload.router, payload.config.endpoints);
// Bind router to API
payload.express.use(payload.config.routes.api, payload.router);

View File

@@ -0,0 +1,3 @@
export default function canResizeImage(mimeType: string): boolean {
return ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].indexOf(mimeType) > -1;
}

View File

@@ -1,12 +1,12 @@
import fs from 'fs';
import sharp from 'sharp';
import sanitize from 'sanitize-filename';
import { fromBuffer } from 'file-type';
import { ProbedImageSize } from './getImageSize';
import fileExists from './fileExists';
import fs from 'fs';
import sanitize from 'sanitize-filename';
import sharp from 'sharp';
import { SanitizedCollectionConfig } from '../collections/config/types';
import { FileSizes, ImageSize } from './types';
import { PayloadRequest } from '../express/types';
import fileExists from './fileExists';
import { ProbedImageSize } from './getImageSize';
import { FileSizes, ImageSize } from './types';
type Args = {
req: PayloadRequest
@@ -50,7 +50,7 @@ export default async function resizeAndSave({
const sizes = imageSizes
.filter((desiredSize) => needsResize(desiredSize, dimensions))
.map(async (desiredSize) => {
let resized = await sharp(file).resize(desiredSize);
let resized = sharp(file).resize(desiredSize);
if (desiredSize.formatOptions) {
resized = resized.toFormat(desiredSize.formatOptions.format, desiredSize.formatOptions.options);

View File

@@ -1,18 +1,18 @@
import { fromBuffer } from 'file-type';
import mkdirp from 'mkdirp';
import path from 'path';
import sharp, { Sharp } from 'sharp';
import { fromBuffer } from 'file-type';
import sanitize from 'sanitize-filename';
import { SanitizedConfig } from '../config/types';
import sharp, { Sharp } from 'sharp';
import { Collection } from '../collections/config/types';
import { SanitizedConfig } from '../config/types';
import { FileUploadError, MissingFile } from '../errors';
import { PayloadRequest } from '../express/types';
import { FileData } from './types';
import saveBufferToFile from './saveBufferToFile';
import getImageSize, { ProbedImageSize } from './getImageSize';
import getSafeFileName from './getSafeFilename';
import getImageSize from './getImageSize';
import resizeAndSave from './imageResizer';
import isImage from './isImage';
import saveBufferToFile from './saveBufferToFile';
import { FileData } from './types';
import canResizeImage from './canResizeImage';
type Args = {
config: SanitizedConfig,
@@ -59,54 +59,53 @@ const uploadFile = async ({
if (file) {
try {
const shouldResize = canResizeImage(file.mimetype);
let fsSafeName: string;
let fileBuffer: Buffer;
let mimeType: string;
let fileSize: number;
if (!disableLocalStorage) {
let resized: Sharp | undefined;
let resized: Sharp | undefined;
let dimensions: ProbedImageSize;
if (shouldResize) {
if (resizeOptions) {
resized = sharp(file.data).resize(resizeOptions);
resized = sharp(file.data)
.resize(resizeOptions);
}
if (formatOptions) {
resized = (resized ?? sharp(file.data)).toFormat(formatOptions.format, formatOptions.options);
}
fileBuffer = resized ? (await resized.toBuffer()) : file.data;
const { mime, ext } = await fromBuffer(fileBuffer);
mimeType = mime;
fileSize = fileBuffer.length;
const baseFilename = sanitize(file.name.substring(0, file.name.lastIndexOf('.')) || file.name);
fsSafeName = `${baseFilename}.${ext}`;
dimensions = await getImageSize(file);
fileData.width = dimensions.width;
fileData.height = dimensions.height;
}
if (!overwriteExistingFiles) {
fsSafeName = await getSafeFileName(Model, staticPath, fsSafeName);
}
const fileBuffer = resized ? (await resized.toBuffer()) : file.data;
const { mime, ext } = await fromBuffer(fileBuffer) ?? { mime: file.mimetype, ext: file.name.split('.').pop() };
const fileSize = fileBuffer.length;
const baseFilename = sanitize(file.name.substring(0, file.name.lastIndexOf('.')) || file.name);
fsSafeName = `${baseFilename}.${ext}`;
if (!overwriteExistingFiles) {
fsSafeName = await getSafeFileName(Model, staticPath, fsSafeName);
}
if (!disableLocalStorage) {
await saveBufferToFile(fileBuffer, `${staticPath}/${fsSafeName}`);
}
fileData.filename = fsSafeName || (!overwriteExistingFiles ? await getSafeFileName(Model, staticPath, file.name) : file.name);
fileData.filesize = fileSize || file.size;
fileData.mimeType = mimeType || (await fromBuffer(file.data)).mime;
fileData.mimeType = mime || (await fromBuffer(file.data)).mime;
if (isImage(file.mimetype)) {
const dimensions = await getImageSize(file);
fileData.width = dimensions.width;
fileData.height = dimensions.height;
if (Array.isArray(imageSizes) && file.mimetype !== 'image/svg+xml') {
req.payloadUploadSizes = {};
fileData.sizes = await resizeAndSave({
req,
file: file.data,
dimensions,
staticPath,
config: collectionConfig,
savedFilename: fsSafeName,
mimeType: fileData.mimeType,
});
}
if (Array.isArray(imageSizes) && shouldResize) {
req.payloadUploadSizes = {};
fileData.sizes = await resizeAndSave({
req,
file: file.data,
dimensions,
staticPath,
config: collectionConfig,
savedFilename: fsSafeName || file.name,
mimeType: fileData.mimeType,
});
}
} catch (err) {
console.error(err);