merges
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import crypto from 'crypto';
|
||||
import { AfterForgotPasswordHook, BeforeOperationHook } from '../../collections/config/types';
|
||||
import { APIError } from '../../errors';
|
||||
|
||||
async function forgotPassword(incomingArgs) {
|
||||
const { config, sendEmail: email } = this;
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(incomingArgs.data, 'email')) {
|
||||
throw new APIError('Missing email.');
|
||||
throw new APIError('Missing email.', 400);
|
||||
}
|
||||
|
||||
let args = incomingArgs;
|
||||
@@ -14,7 +15,7 @@ async function forgotPassword(incomingArgs) {
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => {
|
||||
await priorHook;
|
||||
|
||||
args = (await hook({
|
||||
@@ -38,7 +39,7 @@ async function forgotPassword(incomingArgs) {
|
||||
// Forget password
|
||||
// /////////////////////////////////////
|
||||
|
||||
let token = crypto.randomBytes(20);
|
||||
let token: string | Buffer = crypto.randomBytes(20);
|
||||
token = token.toString('hex');
|
||||
|
||||
const user = await Model.findOne({ email: data.email.toLowerCase() });
|
||||
@@ -90,7 +91,7 @@ async function forgotPassword(incomingArgs) {
|
||||
// afterForgotPassword - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterForgotPassword.reduce(async (priorHook, hook) => {
|
||||
await collectionConfig.hooks.afterForgotPassword.reduce(async (priorHook: AfterForgotPasswordHook, hook: AfterForgotPasswordHook) => {
|
||||
await priorHook;
|
||||
await hook({ args });
|
||||
}, Promise.resolve());
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AuthenticationError, LockedAuth } from '../../errors';
|
||||
import getCookieExpiration from '../../utilities/getCookieExpiration';
|
||||
import isLocked from '../isLocked';
|
||||
import removeInternalFields from '../../utilities/removeInternalFields';
|
||||
import { BeforeLoginHook, BeforeOperationHook } from '../../collections/config/types';
|
||||
|
||||
async function login(incomingArgs) {
|
||||
const { config, operations, secret } = this;
|
||||
@@ -13,7 +14,7 @@ async function login(incomingArgs) {
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => {
|
||||
await priorHook;
|
||||
|
||||
args = (await hook({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { BeforeOperationHook } from '../../collections/config/types';
|
||||
import { Forbidden } from '../../errors';
|
||||
import getCookieExpiration from '../../utilities/getCookieExpiration';
|
||||
|
||||
@@ -9,7 +10,7 @@ async function refresh(incomingArgs) {
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => {
|
||||
await priorHook;
|
||||
|
||||
args = (await hook({
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { Access, Hook } from '../../config/types';
|
||||
/* eslint-disable no-use-before-define */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Access } from '../../config/types';
|
||||
import { Field } from '../../fields/config/types';
|
||||
import { PayloadRequest } from '../../express/types/payloadRequest';
|
||||
|
||||
export type ImageSize = {
|
||||
name: string,
|
||||
width: number,
|
||||
height: number,
|
||||
crop: string, // comes from sharp package
|
||||
};
|
||||
|
||||
export type Collection = {
|
||||
slug: string;
|
||||
labels: {
|
||||
@@ -22,18 +17,17 @@ export type Collection = {
|
||||
components: any;
|
||||
};
|
||||
hooks: {
|
||||
beforeOperation: Hook[];
|
||||
beforeValidate: Hook[];
|
||||
beforeChange: Hook[];
|
||||
afterChange: Hook[];
|
||||
beforeRead: Hook[];
|
||||
afterRead: Hook[];
|
||||
beforeDelete: Hook[];
|
||||
afterDelete?: Hook[];
|
||||
beforeLogin?: Hook[];
|
||||
afterLogin?: Hook[];
|
||||
afterForgotPassword?: Hook[];
|
||||
forgotPassword?: Hook[];
|
||||
beforeOperation?: BeforeOperationHook[];
|
||||
beforeValidate?: BeforeValidateHook[];
|
||||
beforeChange?: BeforeChangeHook[];
|
||||
afterChange?: AfterChangeHook[];
|
||||
beforeRead?: BeforeReadHook[];
|
||||
afterRead?: AfterReadHook[];
|
||||
beforeDelete?: BeforeDeleteHook[];
|
||||
afterDelete?: AfterDeleteHook[];
|
||||
beforeLogin?: BeforeLoginHook[];
|
||||
afterLogin?: AfterLoginHook[];
|
||||
afterForgotPassword?: AfterForgotPasswordHook[];
|
||||
};
|
||||
access: {
|
||||
create: Access;
|
||||
@@ -70,3 +64,83 @@ export type Collection = {
|
||||
adminThumbnail?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ImageSize = {
|
||||
name: string,
|
||||
width: number,
|
||||
height: number,
|
||||
crop: string, // comes from sharp package
|
||||
};
|
||||
|
||||
// Hooks
|
||||
|
||||
export type HookOperationType =
|
||||
| 'create'
|
||||
| 'read'
|
||||
| 'update'
|
||||
| 'delete'
|
||||
| 'refresh'
|
||||
| 'login'
|
||||
| 'forgotPassword';
|
||||
|
||||
export type BeforeOperationHook = (args?: {
|
||||
args?: any;
|
||||
operation: HookOperationType;
|
||||
}) => any;
|
||||
|
||||
export type BeforeValidateHook = (args?: {
|
||||
data?: any;
|
||||
req?: PayloadRequest;
|
||||
operation: 'create' | 'update';
|
||||
originalDoc?: any; // undefined on 'create' operation
|
||||
}) => any;
|
||||
|
||||
export type BeforeChangeHook = (args?: {
|
||||
data: any;
|
||||
req: PayloadRequest;
|
||||
operation: 'create' | 'update'
|
||||
originalDoc?: any; // undefined on 'create' operation
|
||||
}) => any;
|
||||
|
||||
export type AfterChangeHook = (args?: {
|
||||
doc: any;
|
||||
req: PayloadRequest;
|
||||
operation: 'create' | 'update';
|
||||
}) => any;
|
||||
|
||||
export type BeforeReadHook = (args?: {
|
||||
doc: any;
|
||||
req: PayloadRequest;
|
||||
query: { [key: string]: any };
|
||||
}) => any;
|
||||
|
||||
export type AfterReadHook = (args?: {
|
||||
doc: any;
|
||||
req: PayloadRequest;
|
||||
query: { [key: string]: any };
|
||||
}) => any;
|
||||
|
||||
export type BeforeDeleteHook = (args?: {
|
||||
req: PayloadRequest;
|
||||
id: string;
|
||||
}) => any;
|
||||
|
||||
export type AfterDeleteHook = (args?: {
|
||||
req: PayloadRequest;
|
||||
id: string;
|
||||
doc: any;
|
||||
}) => any;
|
||||
|
||||
export type BeforeLoginHook = (args?: {
|
||||
req: PayloadRequest;
|
||||
}) => any;
|
||||
|
||||
export type AfterLoginHook = (args?: {
|
||||
req: PayloadRequest;
|
||||
user: any;
|
||||
token: string;
|
||||
}) => any;
|
||||
|
||||
export type AfterForgotPasswordHook = (args?: {
|
||||
args?: any;
|
||||
}) => any;
|
||||
|
||||
@@ -2,13 +2,17 @@ import mongoose from 'mongoose';
|
||||
import express from 'express';
|
||||
import passport from 'passport';
|
||||
import passportLocalMongoose from 'passport-local-mongoose';
|
||||
const LocalStrategy = require('passport-local').Strategy;
|
||||
import Passport from 'passport-local';
|
||||
import { UpdateQuery } from 'mongodb';
|
||||
import apiKeyStrategy from '../auth/strategies/apiKey';
|
||||
import buildSchema from './buildSchema';
|
||||
import bindCollectionMiddleware from './bindCollection';
|
||||
import { Collection } from './config/types';
|
||||
|
||||
function registerCollections() {
|
||||
this.config.collections = this.config.collections.map((collection) => {
|
||||
const LocalStrategy = Passport.Strategy;
|
||||
|
||||
export default function registerCollections(): void {
|
||||
this.config.collections = this.config.collections.map((collection: Collection) => {
|
||||
const formattedCollection = collection;
|
||||
|
||||
const schema = buildSchema(formattedCollection, this.config);
|
||||
@@ -32,7 +36,8 @@ function registerCollections() {
|
||||
}, cb);
|
||||
}
|
||||
|
||||
const updates = { $inc: { loginAttempts: 1 } };
|
||||
type LoginSchema = { loginAttempts: number; };
|
||||
const updates: UpdateQuery<LoginSchema> = { $inc: { loginAttempts: 1 } };
|
||||
// Lock the account if at max attempts and not already locked
|
||||
if (this.loginAttempts + 1 >= maxLoginAttempts && !this.isLocked) {
|
||||
updates.$set = { lockUntil: Date.now() + lockTime };
|
||||
@@ -151,5 +156,3 @@ function registerCollections() {
|
||||
return formattedCollection;
|
||||
});
|
||||
}
|
||||
|
||||
export default registerCollections;
|
||||
|
||||
@@ -9,9 +9,11 @@ import { MissingFile, FileUploadError } from '../../errors';
|
||||
import resizeAndSave from '../../uploads/imageResizer';
|
||||
import getSafeFilename from '../../uploads/getSafeFilename';
|
||||
import getImageSize from '../../uploads/getImageSize';
|
||||
import imageMIMETypes from '../../uploads/imageMIMETypes';
|
||||
import isImage from '../../uploads/isImage';
|
||||
import { FileData } from '../../uploads/types';
|
||||
|
||||
import sendVerificationEmail from '../../auth/sendVerificationEmail';
|
||||
import { AfterChangeHook, BeforeOperationHook, BeforeValidateHook } from '../config/types';
|
||||
|
||||
async function create(incomingArgs) {
|
||||
const { performFieldOperations, config } = this;
|
||||
@@ -22,7 +24,7 @@ async function create(incomingArgs) {
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => {
|
||||
await priorHook;
|
||||
|
||||
args = (await hook({
|
||||
@@ -73,7 +75,7 @@ async function create(incomingArgs) {
|
||||
// beforeValidate - Collections
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
|
||||
await collectionConfig.hooks.beforeValidate.reduce(async (priorHook: BeforeValidateHook, hook: BeforeValidateHook) => {
|
||||
await priorHook;
|
||||
|
||||
data = (await hook({
|
||||
@@ -113,8 +115,9 @@ async function create(incomingArgs) {
|
||||
// Upload and resize potential files
|
||||
// /////////////////////////////////////
|
||||
|
||||
|
||||
if (collectionConfig.upload) {
|
||||
const fileData = {};
|
||||
const fileData: Partial<FileData> = {};
|
||||
|
||||
const { staticDir, imageSizes } = collectionConfig.upload;
|
||||
|
||||
@@ -137,7 +140,7 @@ async function create(incomingArgs) {
|
||||
try {
|
||||
await file.mv(`${staticPath}/${fsSafeName}`);
|
||||
|
||||
if (imageMIMETypes.indexOf(file.mimetype) > -1) {
|
||||
if (isImage(file.mimetype)) {
|
||||
const dimensions = await getImageSize(`${staticPath}/${fsSafeName}`);
|
||||
fileData.width = dimensions.width;
|
||||
fileData.height = dimensions.height;
|
||||
@@ -148,7 +151,7 @@ async function create(incomingArgs) {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new FileUploadError(err);
|
||||
throw new FileUploadError();
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +217,7 @@ async function create(incomingArgs) {
|
||||
// afterChange - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
|
||||
await collectionConfig.hooks.afterChange.reduce(async (priorHook: AfterChangeHook, hook: AfterChangeHook) => {
|
||||
await priorHook;
|
||||
|
||||
result = await hook({
|
||||
|
||||
@@ -5,6 +5,7 @@ import removeInternalFields from '../../utilities/removeInternalFields';
|
||||
import { NotFound, Forbidden, ErrorDeletingFile } from '../../errors';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import fileExists from '../../uploads/fileExists';
|
||||
import { BeforeOperationHook } from '../config/types';
|
||||
|
||||
async function deleteQuery(incomingArgs) {
|
||||
let args = incomingArgs;
|
||||
@@ -13,7 +14,7 @@ async function deleteQuery(incomingArgs) {
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => {
|
||||
await priorHook;
|
||||
|
||||
args = (await hook({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import removeInternalFields from '../../utilities/removeInternalFields';
|
||||
import { BeforeOperationHook, BeforeReadHook } from '../config/types';
|
||||
|
||||
async function find(incomingArgs) {
|
||||
let args = incomingArgs;
|
||||
@@ -8,7 +9,7 @@ async function find(incomingArgs) {
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => {
|
||||
await priorHook;
|
||||
|
||||
args = (await hook({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import memoize from 'micro-memoize';
|
||||
import { BeforeOperationHook } from '../config/types';
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import removeInternalFields from '../../utilities/removeInternalFields';
|
||||
import { Forbidden, NotFound } from '../../errors';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
@@ -11,7 +12,7 @@ async function findByID(incomingArgs) {
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => {
|
||||
await priorHook;
|
||||
|
||||
args = (await hook({
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
import httpStatus from 'http-status';
|
||||
import deepmerge from 'deepmerge';
|
||||
import path from 'path';
|
||||
import { BeforeOperationHook, BeforeChangeHook, BeforeValidateHook } from '../config/types';
|
||||
|
||||
import removeInternalFields from '../../utilities/removeInternalFields';
|
||||
import overwriteMerge from '../../utilities/overwriteMerge';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import { NotFound, Forbidden, APIError, FileUploadError } from '../../errors';
|
||||
import imageMIMETypes from '../../uploads/imageMIMETypes';
|
||||
import isImage from '../../uploads/isImage';
|
||||
import getImageSize from '../../uploads/getImageSize';
|
||||
import getSafeFilename from '../../uploads/getSafeFilename';
|
||||
|
||||
import resizeAndSave from '../../uploads/imageResizer';
|
||||
import { FileData } from '../../uploads/types';
|
||||
|
||||
|
||||
async function update(incomingArgs) {
|
||||
const { performFieldOperations, config } = this;
|
||||
|
||||
let args = incomingArgs;
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => {
|
||||
await priorHook;
|
||||
|
||||
args = (await hook({
|
||||
@@ -59,7 +64,7 @@ async function update(incomingArgs) {
|
||||
// Retrieve document
|
||||
// /////////////////////////////////////
|
||||
|
||||
const queryToBuild = {
|
||||
const queryToBuild: { [key: string]: any } = {
|
||||
where: {
|
||||
and: [
|
||||
{
|
||||
@@ -97,7 +102,7 @@ async function update(incomingArgs) {
|
||||
// beforeValidate - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await this.performFieldOperations(collectionConfig, {
|
||||
data = await performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
req,
|
||||
id,
|
||||
@@ -111,7 +116,7 @@ async function update(incomingArgs) {
|
||||
// beforeValidate - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
|
||||
await collectionConfig.hooks.beforeValidate.reduce(async (priorHook: BeforeValidateHook, hook: BeforeValidateHook) => {
|
||||
await priorHook;
|
||||
|
||||
data = (await hook({
|
||||
@@ -126,7 +131,7 @@ async function update(incomingArgs) {
|
||||
// beforeChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await this.performFieldOperations(collectionConfig, {
|
||||
data = await performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
req,
|
||||
id,
|
||||
@@ -140,7 +145,7 @@ async function update(incomingArgs) {
|
||||
// beforeChange - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
|
||||
await collectionConfig.hooks.beforeChange.reduce(async (priorHook: BeforeChangeHook, hook: BeforeChangeHook) => {
|
||||
await priorHook;
|
||||
|
||||
data = (await hook({
|
||||
@@ -162,14 +167,14 @@ async function update(incomingArgs) {
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (collectionConfig.upload) {
|
||||
const fileData = {};
|
||||
const fileData: Partial<FileData> = {};
|
||||
|
||||
const { staticDir, imageSizes } = collectionConfig.upload;
|
||||
|
||||
let staticPath = staticDir;
|
||||
|
||||
if (staticDir.indexOf('/') !== 0) {
|
||||
staticPath = path.join(this.config.paths.configDir, staticDir);
|
||||
staticPath = path.join(config.paths.configDir, staticDir);
|
||||
}
|
||||
|
||||
const file = (req.files && req.files.file) ? req.files.file : req.fileData;
|
||||
@@ -184,7 +189,7 @@ async function update(incomingArgs) {
|
||||
fileData.filesize = file.size;
|
||||
fileData.mimeType = file.mimetype;
|
||||
|
||||
if (imageMIMETypes.indexOf(file.mimetype) > -1) {
|
||||
if (isImage(file.mimetype)) {
|
||||
const dimensions = await getImageSize(`${staticPath}/${fsSafeName}`);
|
||||
fileData.width = dimensions.width;
|
||||
fileData.height = dimensions.height;
|
||||
@@ -194,10 +199,9 @@ async function update(incomingArgs) {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
throw new FileUploadError(err);
|
||||
throw new FileUploadError();
|
||||
}
|
||||
|
||||
|
||||
data = {
|
||||
...data,
|
||||
...fileData,
|
||||
@@ -240,7 +244,7 @@ async function update(incomingArgs) {
|
||||
// afterChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
doc = await this.performFieldOperations(collectionConfig, {
|
||||
doc = await performFieldOperations(collectionConfig, {
|
||||
data: doc,
|
||||
hook: 'afterChange',
|
||||
operation: 'update',
|
||||
@@ -269,7 +273,7 @@ async function update(incomingArgs) {
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
doc = await this.performFieldOperations(collectionConfig, {
|
||||
doc = await performFieldOperations(collectionConfig, {
|
||||
depth,
|
||||
req,
|
||||
data: doc,
|
||||
|
||||
@@ -2,8 +2,14 @@ import { PayloadConfig, Config } from './types';
|
||||
import sanitize from './sanitize';
|
||||
import validate from './validate';
|
||||
|
||||
/**
|
||||
* @description Builds and validates Payload configuration
|
||||
* @param config Payload Config
|
||||
* @returns Built and sanitized Payload Config
|
||||
*/
|
||||
export function buildConfig(config: PayloadConfig): Config {
|
||||
const validatedConfig = validate(config);
|
||||
const sanitizedConfig = sanitize(validatedConfig);
|
||||
return sanitizedConfig;
|
||||
const validated = validate(config);
|
||||
const sanitized = sanitize(validated);
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
@@ -43,18 +43,18 @@ export type MockEmailCredentials = {
|
||||
web: string;
|
||||
};
|
||||
|
||||
export type Hook = (...args: any[]) => any | void;
|
||||
export type Access = (args?: any) => boolean;
|
||||
|
||||
export type PayloadConfig = {
|
||||
admin?: {
|
||||
user?: string
|
||||
user?: string;
|
||||
meta?: {
|
||||
titleSuffix?: string
|
||||
ogImage?: string
|
||||
favicon?: string
|
||||
titleSuffix?: string;
|
||||
ogImage?: string;
|
||||
favicon?: string;
|
||||
}
|
||||
disable?: boolean
|
||||
disable?: boolean;
|
||||
indexHTML?: string;
|
||||
};
|
||||
collections?: Collection[];
|
||||
globals?: Global[];
|
||||
@@ -69,7 +69,7 @@ export type PayloadConfig = {
|
||||
graphQL?: string;
|
||||
graphQLPlayground?: string;
|
||||
};
|
||||
express: {
|
||||
express?: {
|
||||
json: {
|
||||
limit?: number
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class APIError extends ExtendableError {
|
||||
* @param {object} data - response data to be returned.
|
||||
* @param {boolean} isPublic - Whether the message should be visible to user or not.
|
||||
*/
|
||||
constructor(message: string, status: number = httpStatus.INTERNAL_SERVER_ERROR, data: any, isPublic = false) {
|
||||
constructor(message: string, status: number = httpStatus.INTERNAL_SERVER_ERROR, data: any = null, isPublic = false) {
|
||||
super(message, status, data, isPublic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Field } from '../fields/config/types';
|
||||
import APIError from './APIError';
|
||||
|
||||
class InvalidFieldRelationship extends APIError {
|
||||
constructor(field, relationship) {
|
||||
constructor(field: Field, relationship: string) {
|
||||
super(`Field ${field.label} has invalid relationship '${relationship}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import httpStatus from 'http-status';
|
||||
import { Response, NextFunction } from 'express';
|
||||
import formatErrorResponse from '../responses/formatError';
|
||||
import { PayloadRequest } from '../types/payloadRequest';
|
||||
|
||||
const errorHandler = (config, logger) => async (err, req, res, next) => {
|
||||
const errorHandler = (config, logger) => async (err, req: PayloadRequest, res: Response): Promise<void> => {
|
||||
const data = formatErrorResponse(err);
|
||||
let response;
|
||||
let status = err.status || httpStatus.INTERNAL_SERVER_ERROR;
|
||||
|
||||
@@ -9,8 +9,9 @@ import rateLimit from 'express-rate-limit';
|
||||
import localizationMiddleware from '../../localization/middleware';
|
||||
import authenticate from './authenticate';
|
||||
import identifyAPI from './identifyAPI';
|
||||
import { Payload } from '../..';
|
||||
|
||||
const middleware = (payload) => {
|
||||
const middleware = (payload: Payload) => {
|
||||
const rateLimitOptions = {
|
||||
windowMs: payload.config.rateLimit.window,
|
||||
max: payload.config.rateLimit.max,
|
||||
|
||||
@@ -9,7 +9,8 @@ export type FieldHook = (args: {
|
||||
originalDoc?: any,
|
||||
data?: any,
|
||||
operation?: 'create' | 'update',
|
||||
req?: PayloadRequest}) => Promise<any> | any;
|
||||
req?: PayloadRequest
|
||||
}) => Promise<any> | any;
|
||||
|
||||
type FieldBase = {
|
||||
name: string;
|
||||
@@ -136,4 +137,20 @@ export type BlockField = FieldBase & {
|
||||
blocks?: Block[];
|
||||
};
|
||||
|
||||
export type Field = NumberField | TextField | EmailField | TextareaField | CodeField | CheckboxField | DateField | BlockField | RadioField | RelationshipField | ArrayField | RichTextField | GroupField | RowField | SelectField | SelectManyField | UploadField;
|
||||
export type Field = NumberField
|
||||
| TextField
|
||||
| EmailField
|
||||
| TextareaField
|
||||
| CodeField
|
||||
| CheckboxField
|
||||
| DateField
|
||||
| BlockField
|
||||
| RadioField
|
||||
| RelationshipField
|
||||
| ArrayField
|
||||
| RichTextField
|
||||
| GroupField
|
||||
| RowField
|
||||
| SelectField
|
||||
| SelectManyField
|
||||
| UploadField;
|
||||
|
||||
@@ -4,8 +4,7 @@ import traverseFields from './traverseFields';
|
||||
import { Collection } from '../collections/config/types';
|
||||
import { OperationArguments } from '../types';
|
||||
|
||||
|
||||
export default async function performFieldOperations(entityConfig: Collection, args: OperationArguments) {
|
||||
export default async function performFieldOperations(entityConfig: Collection, args: OperationArguments): any {
|
||||
const {
|
||||
data: fullData,
|
||||
originalDoc: fullOriginalDoc,
|
||||
@@ -29,7 +28,7 @@ export default async function performFieldOperations(entityConfig: Collection, a
|
||||
let depth = 0;
|
||||
|
||||
if (payloadAPI === 'REST' || payloadAPI === 'local') {
|
||||
depth = (args.depth || args.depth === 0) ? parseInt(args.depth, 10) : this.config.defaultDepth;
|
||||
depth = (args.depth || args.depth === 0) ? parseInt(String(args.depth), 10) : this.config.defaultDepth;
|
||||
|
||||
if (depth > this.config.maxDepth) depth = this.config.maxDepth;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,40 @@ import defaultRichTextValue from './richText/defaultValue';
|
||||
|
||||
const defaultMessage = 'This field is required.';
|
||||
|
||||
export const number = (value, options = {}) => {
|
||||
type NumberOptions = {
|
||||
required?: boolean;
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
type FieldOptions = {
|
||||
required?: boolean;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
}
|
||||
|
||||
type RowOptions = {
|
||||
required?: boolean;
|
||||
minRows?: number;
|
||||
maxRows?: number;
|
||||
}
|
||||
|
||||
type SelectAndRadioOptions = {
|
||||
required?: boolean;
|
||||
options?: {
|
||||
value: string
|
||||
}[];
|
||||
}
|
||||
|
||||
type RequiredOption = { required?: boolean };
|
||||
|
||||
type Validator = (value?: any, options?: NumberOptions |
|
||||
FieldOptions |
|
||||
RequiredOption |
|
||||
RowOptions |
|
||||
SelectAndRadioOptions) => string | boolean;
|
||||
|
||||
export const number: Validator = (value: string, options: NumberOptions = {}) => {
|
||||
const parsedValue = parseInt(value, 10);
|
||||
|
||||
if ((value && typeof parsedValue !== 'number') || (options.required && Number.isNaN(parsedValue))) {
|
||||
@@ -24,7 +57,7 @@ export const number = (value, options = {}) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const text = (value, options = {}) => {
|
||||
export const text: Validator = (value, options: FieldOptions = {}) => {
|
||||
if (options.maxLength && (value && value.length > options.maxLength)) {
|
||||
return `This value must be shorter than the max length of ${options.max} characters.`;
|
||||
}
|
||||
@@ -42,13 +75,13 @@ export const text = (value, options = {}) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const password = (value, options = {}) => {
|
||||
export const password: Validator = (value, options: FieldOptions = {}) => {
|
||||
if (options.maxLength && value.length > options.maxLength) {
|
||||
return `This value must be shorter than the max length of ${options.max} characters.`;
|
||||
return `This value must be shorter than the max length of ${options.maxLength} characters.`;
|
||||
}
|
||||
|
||||
if (options.minLength && value.length < options.minLength) {
|
||||
return `This value must be longer than the minimum length of ${options.max} characters.`;
|
||||
return `This value must be longer than the minimum length of ${options.maxLength} characters.`;
|
||||
}
|
||||
|
||||
if (options.required && !value) {
|
||||
@@ -58,7 +91,7 @@ export const password = (value, options = {}) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const email = (value, options = {}) => {
|
||||
export const email: Validator = (value, options: FieldOptions = {}) => {
|
||||
if ((value && !/\S+@\S+\.\S+/.test(value))
|
||||
|| (!value && options.required)) {
|
||||
return 'Please enter a valid email address.';
|
||||
@@ -67,13 +100,13 @@ export const email = (value, options = {}) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const textarea = (value, options = {}) => {
|
||||
export const textarea: Validator = (value, options: FieldOptions = {}) => {
|
||||
if (options.maxLength && value.length > options.maxLength) {
|
||||
return `This value must be shorter than the max length of ${options.max} characters.`;
|
||||
return `This value must be shorter than the max length of ${options.maxLength} characters.`;
|
||||
}
|
||||
|
||||
if (options.minLength && value.length < options.minLength) {
|
||||
return `This value must be longer than the minimum length of ${options.max} characters.`;
|
||||
return `This value must be longer than the minimum length of ${options.maxLength} characters.`;
|
||||
}
|
||||
|
||||
if (options.required && !value) {
|
||||
@@ -83,7 +116,7 @@ export const textarea = (value, options = {}) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const wysiwyg = (value, options = {}) => {
|
||||
export const wysiwyg: Validator = (value, options: RequiredOption = {}) => {
|
||||
if (options.required && !value) {
|
||||
return defaultMessage;
|
||||
}
|
||||
@@ -91,7 +124,7 @@ export const wysiwyg = (value, options = {}) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const code = (value, options = {}) => {
|
||||
export const code: Validator = (value, options: RequiredOption = {}) => {
|
||||
if (options.required && value === undefined) {
|
||||
return defaultMessage;
|
||||
}
|
||||
@@ -99,7 +132,7 @@ export const code = (value, options = {}) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const richText = (value, options) => {
|
||||
export const richText: Validator = (value, options: RequiredOption = {}) => {
|
||||
if (options.required) {
|
||||
const stringifiedDefaultValue = JSON.stringify(defaultRichTextValue);
|
||||
if (value && JSON.stringify(value) !== stringifiedDefaultValue) return true;
|
||||
@@ -109,7 +142,7 @@ export const richText = (value, options) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const checkbox = (value, options = {}) => {
|
||||
export const checkbox: Validator = (value, options: RequiredOption = {}) => {
|
||||
if ((value && typeof value !== 'boolean')
|
||||
|| (options.required && typeof value !== 'boolean')) {
|
||||
return 'This field can only be equal to true or false.';
|
||||
@@ -118,7 +151,7 @@ export const checkbox = (value, options = {}) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const date = (value, options = {}) => {
|
||||
export const date: Validator = (value, options: RequiredOption = {}) => {
|
||||
if (value && !isNaN(Date.parse(value.toString()))) { /* eslint-disable-line */
|
||||
return true;
|
||||
}
|
||||
@@ -134,17 +167,17 @@ export const date = (value, options = {}) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const upload = (value, options = {}) => {
|
||||
export const upload: Validator = (value, options: RequiredOption = {}) => {
|
||||
if (value || !options.required) return true;
|
||||
return defaultMessage;
|
||||
};
|
||||
|
||||
export const relationship = (value, options = {}) => {
|
||||
export const relationship: Validator = (value, options = {}) => {
|
||||
if (value || !options.required) return true;
|
||||
return defaultMessage;
|
||||
};
|
||||
|
||||
export const array = (value, options = {}) => {
|
||||
export const array: Validator = (value, options: RowOptions = {}) => {
|
||||
if (options.minRows && value < options.minRows) {
|
||||
return `This field requires at least ${options.minRows} row(s).`;
|
||||
}
|
||||
@@ -160,7 +193,7 @@ export const array = (value, options = {}) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const select = (value, options = {}) => {
|
||||
export const select: Validator = (value, options: SelectAndRadioOptions = {}) => {
|
||||
if (Array.isArray(value) && value.find((input) => !options.options.find((option) => (option === input || option.value === input)))) {
|
||||
return 'This field has an invalid selection';
|
||||
}
|
||||
@@ -176,13 +209,13 @@ export const select = (value, options = {}) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const radio = (value, options = {}) => {
|
||||
export const radio: Validator = (value, options: SelectAndRadioOptions = {}) => {
|
||||
const stringValue = String(value);
|
||||
if ((typeof value !== 'undefined' || !options.required) && (options.options.find((option) => String(option.value) === stringValue))) return true;
|
||||
return defaultMessage;
|
||||
};
|
||||
|
||||
export const blocks = (value, options) => {
|
||||
export const blocks: Validator = (value, options: RowOptions = {}) => {
|
||||
if (options.minRows && value < options.minRows) {
|
||||
return `This field requires at least ${options.minRows} row(s).`;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import mongoose from 'mongoose';
|
||||
import buildSchema from '../mongoose/buildSchema';
|
||||
import localizationPlugin from '../localization/plugin';
|
||||
import { Config } from '../config/types';
|
||||
|
||||
const buildModel = (config) => {
|
||||
const buildModel = (config: Config): mongoose.PaginateModel<any> | null => {
|
||||
if (config.globals && config.globals.length > 0) {
|
||||
const globalsSchema = new mongoose.Schema({}, { discriminatorKey: 'globalType', timestamps: true });
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import express from 'express';
|
||||
import buildModel from './buildModel';
|
||||
|
||||
function initGlobals() {
|
||||
export default function initGlobals(): void {
|
||||
if (this.config.globals) {
|
||||
this.globals = {
|
||||
Model: buildModel(this.config),
|
||||
@@ -23,5 +23,3 @@ function initGlobals() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default initGlobals;
|
||||
|
||||
@@ -2,6 +2,7 @@ import deepmerge from 'deepmerge';
|
||||
import overwriteMerge from '../../utilities/overwriteMerge';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import removeInternalFields from '../../utilities/removeInternalFields';
|
||||
import { AfterChangeHook, BeforeValidateHook } from '../../collections/config/types';
|
||||
|
||||
async function update(args) {
|
||||
const { globals: { Model } } = this;
|
||||
@@ -53,7 +54,7 @@ async function update(args) {
|
||||
// 3. Execute before validate collection hooks
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
|
||||
await globalConfig.hooks.beforeValidate.reduce(async (priorHook: BeforeValidateHook, hook: BeforeValidateHook) => {
|
||||
await priorHook;
|
||||
|
||||
data = (await hook({
|
||||
@@ -123,7 +124,7 @@ async function update(args) {
|
||||
// 9. Execute after global hook
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
|
||||
await globalConfig.hooks.afterChange.reduce(async (priorHook: AfterChangeHook, hook: AfterChangeHook) => {
|
||||
await priorHook;
|
||||
|
||||
global = await hook({
|
||||
|
||||
32
src/index.ts
32
src/index.ts
@@ -18,6 +18,7 @@ import {
|
||||
FindByIDOptions,
|
||||
UpdateOptions,
|
||||
DeleteOptions,
|
||||
FindResponse,
|
||||
} from './types';
|
||||
import Logger, { PayloadLogger } from './utilities/logger';
|
||||
import bindOperations from './init/bindOperations';
|
||||
@@ -46,6 +47,9 @@ import { PayloadRequest } from './express/types/payloadRequest';
|
||||
|
||||
require('isomorphic-fetch');
|
||||
|
||||
/**
|
||||
* @description Payload
|
||||
*/
|
||||
export class Payload {
|
||||
config: Config;
|
||||
|
||||
@@ -90,7 +94,11 @@ export class Payload {
|
||||
performFieldOperations: typeof performFieldOperations;
|
||||
// requestHandlers: { collections: { create: any; find: any; findByID: any; update: any; delete: any; auth: { access: any; forgotPassword: any; init: any; login: any; logout: any; me: any; refresh: any; registerFirstUser: any; resetPassword: any; verifyEmail: any; unlock: any; }; }; globals: { ...; }; };
|
||||
|
||||
init(options: InitOptions) {
|
||||
/**
|
||||
* @description Initializes Payload
|
||||
* @param options
|
||||
*/
|
||||
init(options: InitOptions): void {
|
||||
this.logger = Logger();
|
||||
this.logger.info('Starting Payload...');
|
||||
|
||||
@@ -217,13 +225,23 @@ export class Payload {
|
||||
return email.account;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Performs create operation
|
||||
* @param options
|
||||
* @returns created document
|
||||
*/
|
||||
async create(options: CreateOptions): Promise<any> {
|
||||
let { create } = localOperations;
|
||||
create = create.bind(this);
|
||||
return create(options);
|
||||
}
|
||||
|
||||
async find(options: FindOptions): Promise<any> {
|
||||
/**
|
||||
* @description Find documents with criteria
|
||||
* @param options
|
||||
* @returns documents satisfying query
|
||||
*/
|
||||
async find(options: FindOptions): Promise<FindResponse> {
|
||||
let { find } = localOperations;
|
||||
find = find.bind(this);
|
||||
return find(options);
|
||||
@@ -241,12 +259,22 @@ export class Payload {
|
||||
return update(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find document by ID
|
||||
* @param options
|
||||
* @returns document with specified ID
|
||||
*/
|
||||
async findByID(options: FindByIDOptions): Promise<any> {
|
||||
let { findByID } = localOperations;
|
||||
findByID = findByID.bind(this);
|
||||
return findByID(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Update document
|
||||
* @param options
|
||||
* @returns Updated document
|
||||
*/
|
||||
async update(options: UpdateOptions): Promise<any> {
|
||||
let { update } = localOperations;
|
||||
update = update.bind(this);
|
||||
|
||||
263
src/payload.config.sample.ts
Normal file
263
src/payload.config.sample.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { BeforeOperationHook, BeforeValidateHook, HookOperationType } from './collections/config/types';
|
||||
import { buildConfig } from './config/build';
|
||||
import { Field, Block, BlockField, RadioField, ArrayField, RichTextField, GroupField, SelectField, SelectManyField, UploadField, RelationshipField } from './fields/config/types';
|
||||
|
||||
const cfg = buildConfig({
|
||||
serverURL: 'localhost:3000',
|
||||
admin: {
|
||||
disable: true,
|
||||
},
|
||||
});
|
||||
|
||||
const beforeOpHook: BeforeOperationHook = ({ args, operation }) => {
|
||||
if (operation === 'create' && args.req.query && typeof args.req.query.checkout !== 'undefined') {
|
||||
return {
|
||||
...args,
|
||||
disableVerificationEmail: true,
|
||||
};
|
||||
}
|
||||
|
||||
return args;
|
||||
};
|
||||
|
||||
const beforeOpHookResult = beforeOpHook({ args: {}, operation: 'create' });
|
||||
|
||||
const beforeValidate: BeforeValidateHook = ({ data, req, operation, originalDoc }) => {
|
||||
if (operation === 'create') {
|
||||
const formattedData = { ...data };
|
||||
const { user } = req;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const TextField: Field = {
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
label: 'Text',
|
||||
required: true,
|
||||
defaultValue: 'Default Value',
|
||||
unique: true,
|
||||
access: {
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
},
|
||||
};
|
||||
|
||||
const NumbersBlock: Block = {
|
||||
slug: 'number',
|
||||
labels: {
|
||||
singular: 'Number',
|
||||
plural: 'Numbers',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'testNumber',
|
||||
label: 'Test Number Field',
|
||||
type: 'number',
|
||||
maxLength: 100,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const CTA: Block = {
|
||||
slug: 'cta',
|
||||
labels: {
|
||||
singular: 'Call to Action',
|
||||
plural: 'Calls to Action',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
label: 'Label',
|
||||
type: 'text',
|
||||
maxLength: 100,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
label: 'URL',
|
||||
type: 'text',
|
||||
height: 100,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
const blockField: BlockField = {
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
label: 'Blocks Content',
|
||||
minRows: 2,
|
||||
blocks: [NumbersBlock, CTA],
|
||||
localized: true,
|
||||
required: true,
|
||||
};
|
||||
|
||||
const radioGroup: RadioField = {
|
||||
name: 'radioGroupExample',
|
||||
label: 'Radio Group Example',
|
||||
type: 'radio',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Options 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}],
|
||||
defaultValue: 'option-2',
|
||||
required: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
};
|
||||
|
||||
const arrayField: ArrayField = {
|
||||
type: 'array',
|
||||
label: 'Array',
|
||||
name: 'array',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
fields: [
|
||||
// {
|
||||
// type: 'row',
|
||||
// fields: [
|
||||
// {
|
||||
// name: 'arrayText1',
|
||||
// label: 'Array Text 1',
|
||||
// type: 'text',
|
||||
// },
|
||||
// {
|
||||
// name: 'arrayText2',
|
||||
// label: 'Array Text 2',
|
||||
// type: 'text',
|
||||
// required: true,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
type: 'text',
|
||||
name: 'arrayText3',
|
||||
label: 'Array Text 3',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
type: 'checkbox',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const richTextField: RichTextField = {
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
label: 'Rich Text',
|
||||
required: true,
|
||||
admin: {
|
||||
elements: [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'blockquote',
|
||||
'ul',
|
||||
'ol',
|
||||
'link',
|
||||
],
|
||||
leaves: [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strikethrough',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const groupField: GroupField = {
|
||||
name: 'group',
|
||||
label: 'Group',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedGroupCustomField',
|
||||
label: 'Nested Group Custom Field',
|
||||
},
|
||||
],
|
||||
};
|
||||
console.log(groupField);
|
||||
|
||||
const selectField: SelectField = {
|
||||
name: 'select',
|
||||
label: 'Select',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}, {
|
||||
value: 'option-4',
|
||||
label: 'Option 4 Label',
|
||||
}],
|
||||
defaultValue: 'option-1',
|
||||
required: true,
|
||||
};
|
||||
|
||||
const selectMany: SelectManyField = {
|
||||
name: 'selectMany',
|
||||
label: 'Select w/ hasMany',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}, {
|
||||
value: 'option-4',
|
||||
label: 'Option 4 Label',
|
||||
}],
|
||||
defaultValue: 'option-1',
|
||||
required: true,
|
||||
hasMany: true,
|
||||
};
|
||||
|
||||
const upload: UploadField = {
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
label: 'Image',
|
||||
relationTo: 'media',
|
||||
};
|
||||
|
||||
const rel1: RelationshipField = {
|
||||
type: 'relationship',
|
||||
label: 'Relationship to One Collection',
|
||||
name: 'relationship',
|
||||
relationTo: 'conditions',
|
||||
hasMany: false,
|
||||
};
|
||||
const rel2: RelationshipField = {
|
||||
type: 'relationship',
|
||||
label: 'Relationship hasMany',
|
||||
name: 'relationshipHasMany',
|
||||
relationTo: ['localized-posts', 'sdf'],
|
||||
hasMany: true,
|
||||
};
|
||||
@@ -15,6 +15,8 @@ export type CreateOptions = {
|
||||
export type FindOptions = {
|
||||
collection: string;
|
||||
where?: { [key: string]: any };
|
||||
depth?: number;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
export type FindResponse = {
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import fs from 'fs';
|
||||
import probeImageSize from 'probe-image-size';
|
||||
|
||||
const getImageSize = async (path) => {
|
||||
type ProbedImageSize = {
|
||||
width: number,
|
||||
height: number,
|
||||
type: string,
|
||||
mime: string,
|
||||
}
|
||||
|
||||
export default async function (path: string): Promise<ProbedImageSize> {
|
||||
const image = fs.createReadStream(path);
|
||||
return probeImageSize(image);
|
||||
};
|
||||
|
||||
export default getImageSize;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import imageMIMETypes from './imageMIMETypes';
|
||||
import isImage from './isImage';
|
||||
|
||||
const getThumbnail = (mimeType, staticURL, filename, sizes, adminThumbnail) => {
|
||||
if (imageMIMETypes.indexOf(mimeType) > -1) {
|
||||
const getThumbnail = (mimeType, staticURL, filename, sizes, adminThumbnail): string | boolean => {
|
||||
if (isImage(mimeType)) {
|
||||
if (sizes?.[adminThumbnail]?.filename) {
|
||||
return `${staticURL}/${sizes[adminThumbnail].filename}`;
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export default ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'];
|
||||
@@ -3,6 +3,8 @@ import sharp from 'sharp';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import getImageSize from './getImageSize';
|
||||
import fileExists from './fileExists';
|
||||
import { Collection } from '../collections/config/types';
|
||||
import { FileSizes } from './types';
|
||||
|
||||
function getOutputImage(sourceImage, size) {
|
||||
const extension = sourceImage.split('.').pop();
|
||||
@@ -16,15 +18,20 @@ function getOutputImage(sourceImage, size) {
|
||||
};
|
||||
}
|
||||
|
||||
export default async function resizeAndSave(staticPath, config, savedFilename, mimeType) {
|
||||
/**
|
||||
* Resize images according to image desired width and height and return sizes
|
||||
* @param config Object
|
||||
* @param uploadConfig Object
|
||||
* @param savedFilename String
|
||||
* @returns String[]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description
|
||||
* @param staticPath Path to save images
|
||||
* @param config Payload config
|
||||
* @param savedFilename
|
||||
* @param mimeType
|
||||
* @returns image sizes keyed to strings
|
||||
*/
|
||||
export default async function resizeAndSave(
|
||||
staticPath: string,
|
||||
config: Collection,
|
||||
savedFilename: string,
|
||||
mimeType: string,
|
||||
): Promise<FileSizes> {
|
||||
const { imageSizes } = config.upload;
|
||||
|
||||
const sourceImage = `${staticPath}/${savedFilename}`;
|
||||
@@ -69,4 +76,4 @@ export default async function resizeAndSave(staticPath, config, savedFilename, m
|
||||
filesize: size.filesize,
|
||||
},
|
||||
}), {});
|
||||
};
|
||||
}
|
||||
|
||||
3
src/uploads/isImage.ts
Normal file
3
src/uploads/isImage.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function isImage(mimeType: string): boolean {
|
||||
return ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'].indexOf(mimeType) > -1;
|
||||
}
|
||||
20
src/uploads/types.ts
Normal file
20
src/uploads/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export type FileSizes = {
|
||||
[size: string]: {
|
||||
filename: string;
|
||||
filesize: number;
|
||||
mimeType: string;
|
||||
name: string;
|
||||
width: number;
|
||||
height: number;
|
||||
crop: string;
|
||||
}
|
||||
}
|
||||
|
||||
export type FileData = {
|
||||
filename: string;
|
||||
filesize: number;
|
||||
mimeType: string;
|
||||
width: number;
|
||||
height: number;
|
||||
sizes: FileSizes;
|
||||
};
|
||||
Reference in New Issue
Block a user