Merge branch 'master' of github.com:payloadcms/payload into feat/form-onchange
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useAuth } from '@payloadcms/config-provider';
|
||||
import Button from '../Button';
|
||||
import { Props } from './types';
|
||||
@@ -6,13 +6,34 @@ import { useLocale } from '../../utilities/Locale';
|
||||
|
||||
const baseClass = 'preview-btn';
|
||||
|
||||
const PreviewButton: React.FC<Props> = ({ generatePreviewURL, data }) => {
|
||||
const PreviewButton: React.FC<Props> = (props) => {
|
||||
const {
|
||||
generatePreviewURL,
|
||||
data
|
||||
} = props;
|
||||
|
||||
const [url, setUrl] = useState<string | undefined>(undefined);
|
||||
|
||||
const locale = useLocale();
|
||||
const { token } = useAuth();
|
||||
|
||||
if (generatePreviewURL && typeof generatePreviewURL === 'function') {
|
||||
const url = generatePreviewURL(data, { locale, token });
|
||||
useEffect(() => {
|
||||
if (generatePreviewURL && typeof generatePreviewURL === 'function') {
|
||||
const makeRequest = async () => {
|
||||
const previewURL = await generatePreviewURL(data, { locale, token });
|
||||
setUrl(previewURL);
|
||||
}
|
||||
|
||||
makeRequest();
|
||||
}
|
||||
}, [
|
||||
generatePreviewURL,
|
||||
locale,
|
||||
token,
|
||||
data
|
||||
]);
|
||||
|
||||
if (url) {
|
||||
return (
|
||||
<Button
|
||||
el="anchor"
|
||||
|
||||
@@ -2,9 +2,9 @@ import React from 'react';
|
||||
|
||||
export type DescriptionFunction = (value: unknown) => string
|
||||
|
||||
export type DescriptionComponent = React.ComponentType<{value: unknown}>
|
||||
export type DescriptionComponent = React.ComponentType<{ value: unknown }>
|
||||
|
||||
type Description = string | DescriptionFunction | DescriptionComponent
|
||||
export type Description = string | DescriptionFunction | DescriptionComponent
|
||||
|
||||
export type Props = {
|
||||
description?: Description
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Data } from '../../Form/types';
|
||||
import { ArrayField, Labels, Field, Description } from '../../../../../fields/config/types';
|
||||
import { ArrayField, Labels, Field } from '../../../../../fields/config/types';
|
||||
import { FieldTypes } from '..';
|
||||
import { FieldPermissions } from '../../../../../auth/types';
|
||||
import { Description } from '../../FieldDescription/types';
|
||||
|
||||
export type Props = Omit<ArrayField, 'type'> & {
|
||||
path?: string
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Data } from '../../Form/types';
|
||||
import { BlockField, Labels, Block, Description } from '../../../../../fields/config/types';
|
||||
import { BlockField, Labels, Block } from '../../../../../fields/config/types';
|
||||
import { FieldTypes } from '..';
|
||||
import { FieldPermissions } from '../../../../../auth/types';
|
||||
import { Description } from '../../FieldDescription/types';
|
||||
|
||||
export type Props = Omit<BlockField, 'type'> & {
|
||||
path?: string
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Description, Validate } from '../../../../../fields/config/types';
|
||||
import { Validate } from '../../../../../fields/config/types';
|
||||
import { Description } from '../../FieldDescription/types';
|
||||
|
||||
export type Props = {
|
||||
autoComplete?: string
|
||||
|
||||
375
src/bin/generateTypes.ts
Normal file
375
src/bin/generateTypes.ts
Normal file
@@ -0,0 +1,375 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import fs from 'fs';
|
||||
import type { JSONSchema4 } from 'json-schema';
|
||||
import { compile } from 'json-schema-to-typescript';
|
||||
import payload from '..';
|
||||
import { fieldAffectsData, Field, Option, FieldAffectingData } from '../fields/config/types';
|
||||
import { SanitizedCollectionConfig } from '../collections/config/types';
|
||||
import { SanitizedConfig } from '../config/types';
|
||||
import loadConfig from '../config/load';
|
||||
import { SanitizedGlobalConfig } from '../globals/config/types';
|
||||
|
||||
function getCollectionIDType(collections: SanitizedCollectionConfig[], slug: string): 'string' | 'number' {
|
||||
const matchedCollection = collections.find((collection) => collection.slug === slug);
|
||||
const customIdField = matchedCollection.fields.find((field) => 'name' in field && field.name === 'id');
|
||||
|
||||
if (customIdField && customIdField.type === 'number') {
|
||||
return 'number';
|
||||
}
|
||||
|
||||
return 'string';
|
||||
}
|
||||
|
||||
function returnOptionEnums(options: Option[]): string[] {
|
||||
return options.map((option) => {
|
||||
if (typeof option === 'object' && 'value' in option) {
|
||||
return option.value;
|
||||
}
|
||||
|
||||
return option;
|
||||
});
|
||||
}
|
||||
|
||||
function generateFieldTypes(config: SanitizedConfig, fields: Field[]): {
|
||||
properties: {
|
||||
[k: string]: JSONSchema4;
|
||||
}
|
||||
required: string[]
|
||||
} {
|
||||
let topLevelProps = [];
|
||||
let requiredTopLevelProps = [];
|
||||
|
||||
return {
|
||||
properties: Object.fromEntries(
|
||||
fields.reduce((properties, field) => {
|
||||
let fieldSchema: JSONSchema4;
|
||||
|
||||
switch (field.type) {
|
||||
case 'text':
|
||||
case 'textarea':
|
||||
case 'code':
|
||||
case 'email':
|
||||
case 'date': {
|
||||
fieldSchema = { type: 'string' };
|
||||
break;
|
||||
}
|
||||
|
||||
case 'number': {
|
||||
fieldSchema = { type: 'number' };
|
||||
break;
|
||||
}
|
||||
|
||||
case 'checkbox': {
|
||||
fieldSchema = { type: 'boolean' };
|
||||
break;
|
||||
}
|
||||
|
||||
case 'richText': {
|
||||
fieldSchema = {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
},
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'radio': {
|
||||
fieldSchema = {
|
||||
type: 'string',
|
||||
enum: returnOptionEnums(field.options),
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'select': {
|
||||
const selectType: JSONSchema4 = {
|
||||
type: 'string',
|
||||
enum: returnOptionEnums(field.options),
|
||||
};
|
||||
|
||||
if (field.hasMany) {
|
||||
fieldSchema = {
|
||||
type: 'array',
|
||||
items: selectType,
|
||||
};
|
||||
} else {
|
||||
fieldSchema = selectType;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'point': {
|
||||
fieldSchema = {
|
||||
type: 'array',
|
||||
minItems: 2,
|
||||
maxItems: 2,
|
||||
items: [
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'relationship': {
|
||||
if (Array.isArray(field.relationTo)) {
|
||||
if (field.hasMany) {
|
||||
fieldSchema = {
|
||||
type: 'array',
|
||||
items: {
|
||||
oneOf: field.relationTo.map((relation) => {
|
||||
const idFieldType = getCollectionIDType(config.collections, relation);
|
||||
|
||||
return {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
value: {
|
||||
oneOf: [
|
||||
{
|
||||
type: idFieldType,
|
||||
},
|
||||
{
|
||||
$ref: `#/definitions/${relation}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
relationTo: {
|
||||
const: relation,
|
||||
},
|
||||
},
|
||||
required: ['value', 'relationTo'],
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
fieldSchema = {
|
||||
oneOf: field.relationTo.map((relation) => {
|
||||
const idFieldType = getCollectionIDType(config.collections, relation);
|
||||
|
||||
return {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
value: {
|
||||
oneOf: [
|
||||
{
|
||||
type: idFieldType,
|
||||
},
|
||||
{
|
||||
$ref: `#/definitions/${relation}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
relationTo: {
|
||||
const: relation,
|
||||
},
|
||||
},
|
||||
required: ['value', 'relationTo'],
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const idFieldType = getCollectionIDType(config.collections, field.relationTo);
|
||||
|
||||
if (field.hasMany) {
|
||||
fieldSchema = {
|
||||
type: 'array',
|
||||
items: {
|
||||
oneOf: [
|
||||
{
|
||||
type: idFieldType,
|
||||
},
|
||||
{
|
||||
$ref: `#/definitions/${field.relationTo}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
} else {
|
||||
fieldSchema = {
|
||||
oneOf: [
|
||||
{
|
||||
type: idFieldType,
|
||||
},
|
||||
{
|
||||
$ref: `#/definitions/${field.relationTo}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'upload': {
|
||||
const idFieldType = getCollectionIDType(config.collections, field.relationTo);
|
||||
|
||||
fieldSchema = {
|
||||
oneOf: [
|
||||
{
|
||||
type: idFieldType,
|
||||
},
|
||||
{
|
||||
$ref: `#/definitions/${field.relationTo}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'blocks': {
|
||||
fieldSchema = {
|
||||
type: 'array',
|
||||
items: {
|
||||
oneOf: field.blocks.map((block) => {
|
||||
const blockSchema = generateFieldTypes(config, block.fields);
|
||||
|
||||
return {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
...blockSchema.properties,
|
||||
blockType: {
|
||||
const: block.slug,
|
||||
},
|
||||
},
|
||||
required: [
|
||||
'blockType',
|
||||
...blockSchema.required,
|
||||
],
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
fieldSchema = {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
...generateFieldTypes(config, field.fields),
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'row': {
|
||||
const topLevelFields = generateFieldTypes(config, field.fields);
|
||||
requiredTopLevelProps = requiredTopLevelProps.concat(topLevelFields.required);
|
||||
topLevelProps = topLevelProps.concat(Object.entries(topLevelFields.properties).map((prop) => prop));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
fieldSchema = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
...generateFieldTypes(config, field.fields),
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldSchema && fieldAffectsData(field)) {
|
||||
return [
|
||||
...properties,
|
||||
[
|
||||
field.name,
|
||||
{
|
||||
...fieldSchema,
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
...properties,
|
||||
...topLevelProps,
|
||||
];
|
||||
}, []),
|
||||
),
|
||||
required: [
|
||||
...fields
|
||||
.filter((field) => fieldAffectsData(field) && field.required === true)
|
||||
.map((field) => (fieldAffectsData(field) ? field.name : '')),
|
||||
...requiredTopLevelProps,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function entityToJsonSchema(config: SanitizedConfig, entity: SanitizedCollectionConfig | SanitizedGlobalConfig): JSONSchema4 {
|
||||
const title = 'label' in entity ? entity.label : entity.labels.singular;
|
||||
|
||||
const idField: FieldAffectingData = { type: 'text', name: 'id', required: true };
|
||||
const customIdField = entity.fields.find((field) => fieldAffectsData(field) && field.name === 'id') as FieldAffectingData;
|
||||
|
||||
if (customIdField) {
|
||||
customIdField.required = true;
|
||||
} else {
|
||||
entity.fields.unshift(idField);
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
...generateFieldTypes(config, entity.fields),
|
||||
};
|
||||
}
|
||||
|
||||
function configToJsonSchema(config: SanitizedConfig): JSONSchema4 {
|
||||
return {
|
||||
definitions: Object.fromEntries(
|
||||
[
|
||||
...config.globals.map((global) => [
|
||||
global.slug,
|
||||
entityToJsonSchema(config, global),
|
||||
]),
|
||||
...config.collections.map((collection) => [
|
||||
collection.slug,
|
||||
entityToJsonSchema(config, collection),
|
||||
]),
|
||||
],
|
||||
),
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function generateTypes(): void {
|
||||
const config = loadConfig();
|
||||
|
||||
payload.logger.info('Compiling TS types for Collections and Globals...');
|
||||
|
||||
const jsonSchema = configToJsonSchema(config);
|
||||
|
||||
compile(jsonSchema, 'Config', {
|
||||
unreachableDefinitions: true,
|
||||
}).then((compiled) => {
|
||||
fs.writeFileSync(config.typescript.outputFile, compiled);
|
||||
payload.logger.info(`Types written to ${config.typescript.outputFile}`);
|
||||
});
|
||||
}
|
||||
|
||||
// when generateTypes.js is launched directly
|
||||
if (module.id === require.main.id) {
|
||||
generateTypes();
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import minimist from 'minimist';
|
||||
import { generateTypes } from './generateTypes';
|
||||
import babelConfig from '../babel.config';
|
||||
|
||||
require('@babel/register')({
|
||||
@@ -23,6 +24,12 @@ switch (script) {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'generate:types': {
|
||||
generateTypes();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
console.log(`Unknown script "${script}".`);
|
||||
break;
|
||||
|
||||
@@ -98,22 +98,28 @@ async function find(incomingArgs: Arguments): Promise<PaginatedDocs> {
|
||||
// Find
|
||||
// /////////////////////////////////////
|
||||
|
||||
let { sort } = args;
|
||||
let sortParam: Record<string, string>;
|
||||
|
||||
if (!sort) {
|
||||
if (!args.sort) {
|
||||
if (collectionConfig.timestamps) {
|
||||
sort = '-createdAt';
|
||||
sortParam = { createdAt: 'desc' };
|
||||
} else {
|
||||
sort = '-_id';
|
||||
sortParam = { _id: 'desc' };
|
||||
}
|
||||
} else if (sort === 'id' || sort === '-id') {
|
||||
sort = sort.replace('id', '_id');
|
||||
} else if (args.sort.indexOf('-') === 0) {
|
||||
sortParam = {
|
||||
[args.sort.substring(1)]: 'desc',
|
||||
};
|
||||
} else {
|
||||
sortParam = {
|
||||
[args.sort]: 'asc',
|
||||
};
|
||||
}
|
||||
|
||||
const optionsToExecute = {
|
||||
page: page || 1,
|
||||
limit: limit || 10,
|
||||
sort,
|
||||
sort: sortParam,
|
||||
lean: true,
|
||||
leanWithId: true,
|
||||
useEstimatedCount,
|
||||
|
||||
@@ -430,12 +430,74 @@ describe('Collections - REST', () => {
|
||||
const data1 = await queryRes1.json();
|
||||
|
||||
expect(data1.docs).toHaveLength(1);
|
||||
});
|
||||
|
||||
const queryRes2 = await fetch(`${url}/api/relationship-a?where[LocalizedPost.en.title][in]=${localizedPostTitle}`);
|
||||
const data2 = await queryRes2.json();
|
||||
it('should allow querying by a localized nested relationship property with many relationTos', async () => {
|
||||
const relationshipBTitle = 'lawleifjawelifjew';
|
||||
const relationshipB = await fetch(`${url}/api/relationship-b?depth=0`, {
|
||||
body: JSON.stringify({
|
||||
title: relationshipBTitle,
|
||||
}),
|
||||
headers,
|
||||
method: 'post',
|
||||
}).then((res) => res.json());
|
||||
|
||||
expect(queryRes2.status).toBe(200);
|
||||
expect(data2.docs).toHaveLength(1);
|
||||
expect(relationshipB.doc.id).toBeDefined();
|
||||
|
||||
const res = await fetch(`${url}/api/relationship-a`, {
|
||||
body: JSON.stringify({
|
||||
postManyRelationships: {
|
||||
value: relationshipB.doc.id,
|
||||
relationTo: 'relationship-b',
|
||||
},
|
||||
}),
|
||||
headers,
|
||||
method: 'post',
|
||||
});
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
|
||||
const queryRes = await fetch(`${url}/api/relationship-a?where[postManyRelationships.value][equals]=${relationshipB.doc.id}`);
|
||||
const data = await queryRes.json();
|
||||
expect(data.docs).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should allow querying by a numeric custom ID', async () => {
|
||||
const customID = 1988;
|
||||
|
||||
const customIDResult = await fetch(`${url}/api/custom-id?depth=0`, {
|
||||
body: JSON.stringify({
|
||||
id: customID,
|
||||
name: 'woohoo',
|
||||
}),
|
||||
headers,
|
||||
method: 'post',
|
||||
}).then((res) => res.json());
|
||||
|
||||
expect(customIDResult.doc.id).toStrictEqual(customID);
|
||||
|
||||
await fetch(`${url}/api/custom-id?depth=0`, {
|
||||
body: JSON.stringify({
|
||||
id: 2343452,
|
||||
name: 'another post',
|
||||
}),
|
||||
headers,
|
||||
method: 'post',
|
||||
}).then((res) => res.json());
|
||||
|
||||
const queryRes1 = await fetch(`${url}/api/custom-id?where[id][equals]=${customID}`, {
|
||||
headers,
|
||||
});
|
||||
|
||||
const data1 = await queryRes1.json();
|
||||
|
||||
expect(data1.docs).toHaveLength(1);
|
||||
|
||||
const queryByIDRes = await fetch(`${url}/api/custom-id/${customID}`, {
|
||||
headers,
|
||||
});
|
||||
const queryByIDData = await queryByIDRes.json();
|
||||
expect(queryByIDData.id).toStrictEqual(customID);
|
||||
});
|
||||
|
||||
it('should allow querying by a field within a group', async () => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { Config, SanitizedConfig } from './types';
|
||||
import sanitize from './sanitize';
|
||||
|
||||
@@ -8,8 +10,14 @@ import sanitize from './sanitize';
|
||||
*/
|
||||
export function buildConfig(config: Config): SanitizedConfig {
|
||||
if (Array.isArray(config.plugins)) {
|
||||
const configWithPlugins = config.plugins.reduce((updatedConfig, plugin) => plugin(updatedConfig), config);
|
||||
return sanitize(configWithPlugins);
|
||||
const configWithPlugins = config.plugins.reduce(
|
||||
(updatedConfig, plugin) => plugin(updatedConfig),
|
||||
config,
|
||||
);
|
||||
|
||||
const sanitizedConfig = sanitize(configWithPlugins);
|
||||
|
||||
return sanitizedConfig;
|
||||
}
|
||||
|
||||
return sanitize(config);
|
||||
|
||||
@@ -19,6 +19,9 @@ export const defaults = {
|
||||
scss: path.resolve(__dirname, '../admin/scss/overrides.scss'),
|
||||
dateFormat: 'MMMM do yyyy, h:mm a',
|
||||
},
|
||||
typescript: {
|
||||
outputFile: `${typeof process?.cwd === 'function' ? process.cwd() : ''}/payload-types.ts`,
|
||||
},
|
||||
upload: {},
|
||||
graphQL: {
|
||||
maxComplexity: 1000,
|
||||
|
||||
@@ -29,6 +29,9 @@ export default joi.object({
|
||||
graphQL: joi.string(),
|
||||
graphQLPlayground: joi.string(),
|
||||
}),
|
||||
typescript: joi.object({
|
||||
outputFile: joi.string(),
|
||||
}),
|
||||
collections: joi.array(),
|
||||
globals: joi.array(),
|
||||
admin: joi.object({
|
||||
|
||||
@@ -25,7 +25,7 @@ type GeneratePreviewURLOptions = {
|
||||
token: string
|
||||
}
|
||||
|
||||
export type GeneratePreviewURL = (doc: Record<string, unknown>, options: GeneratePreviewURLOptions) => string
|
||||
export type GeneratePreviewURL = (doc: Record<string, unknown>, options: GeneratePreviewURLOptions) => Promise<string> | string
|
||||
|
||||
export type EmailTransport = Email & {
|
||||
transport: Transporter;
|
||||
@@ -112,6 +112,9 @@ export type Config = {
|
||||
graphQL?: string;
|
||||
graphQLPlayground?: string;
|
||||
};
|
||||
typescript?: {
|
||||
outputFile?: string
|
||||
}
|
||||
debug?: boolean
|
||||
express?: {
|
||||
json?: {
|
||||
|
||||
@@ -66,6 +66,7 @@ const getBaseUploadFields = ({ config, collection }: Options): Field[] => {
|
||||
name: 'filename',
|
||||
label: 'File Name',
|
||||
type: 'text',
|
||||
index: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
disabled: true,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Editor } from 'slate';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import { Document } from '../../types';
|
||||
import { ConditionalDateProps } from '../../admin/components/elements/DatePicker/types';
|
||||
import { Description } from '../../admin/components/forms/FieldDescription/types';
|
||||
|
||||
export type FieldHook = (args: {
|
||||
value?: unknown,
|
||||
@@ -40,8 +41,6 @@ type Admin = {
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
export type Description = string | ((value: Record<string, unknown>) => string);
|
||||
|
||||
export type Labels = {
|
||||
singular: string;
|
||||
plural: string;
|
||||
|
||||
@@ -154,15 +154,6 @@ class ParamParser {
|
||||
const currentSchemaTypeOptions = getSchemaTypeOptions(currentSchemaType);
|
||||
|
||||
if (currentSchemaTypeOptions.localized) {
|
||||
const upcomingSegment = pathSegments[i + 1];
|
||||
const upcomingPath = `${currentPath}.${upcomingSegment}`;
|
||||
const upcomingSchemaType = schema.path(upcomingPath);
|
||||
|
||||
if (upcomingSchemaType) {
|
||||
lastIncompletePath.path = currentPath;
|
||||
return;
|
||||
}
|
||||
|
||||
const localePath = `${currentPath}.${this.locale}`;
|
||||
const localizedSchemaType = schema.path(localePath);
|
||||
|
||||
@@ -170,6 +161,15 @@ class ParamParser {
|
||||
lastIncompletePath.path = localePath;
|
||||
return;
|
||||
}
|
||||
|
||||
const upcomingSegment = pathSegments[i + 1];
|
||||
const upcomingPathWithLocale = `${currentPath}.${this.locale}.${upcomingSegment}`;
|
||||
const upcomingSchemaTypeWithLocale = schema.path(upcomingPathWithLocale);
|
||||
|
||||
if (upcomingSchemaTypeWithLocale) {
|
||||
lastIncompletePath.path = upcomingPathWithLocale;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lastIncompletePath.path = currentPath;
|
||||
@@ -305,12 +305,10 @@ class ParamParser {
|
||||
}
|
||||
|
||||
if (typeof formattedValue === 'string') {
|
||||
const parsedNumber = parseFloat(formattedValue);
|
||||
|
||||
if (!Number.isNaN(parsedNumber)) {
|
||||
if (!Number.isNaN(formattedValue)) {
|
||||
query.$or.push({
|
||||
[path]: {
|
||||
[operatorKey]: parsedNumber,
|
||||
[operatorKey]: parseFloat(formattedValue),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,6 +107,11 @@ const buildSchema = (config: SanitizedConfig, configFields: Field[], buildSchema
|
||||
}
|
||||
});
|
||||
|
||||
if (buildSchemaOptions?.options?.timestamps) {
|
||||
indexFields.push({ createdAt: 1 });
|
||||
indexFields.push({ updatedAt: 1 });
|
||||
}
|
||||
|
||||
const schema = new Schema(fields, options);
|
||||
indexFields.forEach((index) => {
|
||||
schema.index(index);
|
||||
|
||||
@@ -8,7 +8,7 @@ export const sanitizeQueryValue = (schemaType: SchemaType, path: string, operato
|
||||
|
||||
// Disregard invalid _ids
|
||||
|
||||
if (path === '_id' && typeof val === 'string') {
|
||||
if (path === '_id' && typeof val === 'string' && val.split(',').length === 1) {
|
||||
if (schemaType?.instance === 'ObjectID') {
|
||||
const isValid = mongoose.Types.ObjectId.isValid(val);
|
||||
|
||||
@@ -69,32 +69,29 @@ export const sanitizeQueryValue = (schemaType: SchemaType, path: string, operato
|
||||
}
|
||||
}
|
||||
|
||||
if (['all', 'not_in'].includes(operator) && typeof formattedValue === 'string') {
|
||||
if (['all', 'not_in', 'in'].includes(operator) && typeof formattedValue === 'string') {
|
||||
formattedValue = createArrayFromCommaDelineated(formattedValue);
|
||||
}
|
||||
|
||||
if (schemaOptions && (schemaOptions.ref || schemaOptions.refPath)) {
|
||||
if (operator === 'in') {
|
||||
if (typeof formattedValue === 'string') formattedValue = createArrayFromCommaDelineated(formattedValue);
|
||||
if (Array.isArray(formattedValue)) {
|
||||
formattedValue = formattedValue.reduce((formattedValues, inVal) => {
|
||||
const newValues = [inVal];
|
||||
if (mongoose.Types.ObjectId.isValid(inVal)) newValues.push(new mongoose.Types.ObjectId(inVal));
|
||||
if (schemaOptions && (schemaOptions.ref || schemaOptions.refPath) && operator === 'in') {
|
||||
if (Array.isArray(formattedValue)) {
|
||||
formattedValue = formattedValue.reduce((formattedValues, inVal) => {
|
||||
const newValues = [inVal];
|
||||
if (mongoose.Types.ObjectId.isValid(inVal)) newValues.push(new mongoose.Types.ObjectId(inVal));
|
||||
|
||||
const parsedNumber = parseFloat(inVal);
|
||||
if (!Number.isNaN(parsedNumber)) newValues.push(parsedNumber);
|
||||
const parsedNumber = parseFloat(inVal);
|
||||
if (!Number.isNaN(parsedNumber)) newValues.push(parsedNumber);
|
||||
|
||||
return [
|
||||
...formattedValues,
|
||||
...newValues,
|
||||
];
|
||||
}, []);
|
||||
}
|
||||
return [
|
||||
...formattedValues,
|
||||
...newValues,
|
||||
];
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
|
||||
if (operator === 'like' && path !== '_id') {
|
||||
formattedValue = { $regex: formattedValue, $options: '-i' };
|
||||
formattedValue = { $regex: formattedValue, $options: 'i' };
|
||||
}
|
||||
|
||||
if (operator === 'exists') {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default function isImage(mimeType: string): boolean {
|
||||
return ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'].indexOf(mimeType) > -1;
|
||||
return ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp'].indexOf(mimeType) > -1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user