chore: merge with master
This commit is contained in:
@@ -44,7 +44,7 @@ const Autosave: React.FC<Props> = ({ collection, global, id, publishedDocUpdated
|
||||
modifiedRef.current = modified;
|
||||
|
||||
const createCollectionDoc = useCallback(async () => {
|
||||
const res = await fetch(`${serverURL}${api}/${collection.slug}?locale=${locale}&fallback-locale=null&depth=0&draft=true`, {
|
||||
const res = await fetch(`${serverURL}${api}/${collection.slug}?locale=${locale}&fallback-locale=null&depth=0&draft=true&autosave=true`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
@@ -95,13 +95,13 @@ const Autosave: React.FC<Props> = ({ collection, global, id, publishedDocUpdated
|
||||
}
|
||||
|
||||
if (url) {
|
||||
const body = {
|
||||
...reduceFieldsToValues(fieldRef.current, true),
|
||||
_status: 'draft',
|
||||
};
|
||||
|
||||
setTimeout(async () => {
|
||||
if (modifiedRef.current) {
|
||||
const body = {
|
||||
...reduceFieldsToValues(fieldRef.current, true),
|
||||
_status: 'draft',
|
||||
};
|
||||
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
credentials: 'include',
|
||||
|
||||
@@ -4,9 +4,6 @@ import { useConfig } from '../../utilities/Config';
|
||||
import Button from '../Button';
|
||||
import { Props } from './types';
|
||||
import { useDocumentInfo } from '../../utilities/DocumentInfo';
|
||||
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
|
||||
import { SanitizedGlobalConfig } from '../../../../globals/config/types';
|
||||
import { shouldIncrementVersionCount } from '../../../../versions/shouldIncrementVersionCount';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -14,35 +11,20 @@ const baseClass = 'versions-count';
|
||||
|
||||
const VersionsCount: React.FC<Props> = ({ collection, global, id }) => {
|
||||
const { routes: { admin } } = useConfig();
|
||||
const { versions, publishedDoc, unpublishedVersions } = useDocumentInfo();
|
||||
const { versions } = useDocumentInfo();
|
||||
const { t } = useTranslation('version');
|
||||
|
||||
// Doc status could come from three places:
|
||||
// 1. the newest unpublished version (a draft)
|
||||
// 2. the published doc's status, in the event that the doc is published and there are no newer versions
|
||||
// 3. if there is no published doc, it's a draft
|
||||
const docStatus = unpublishedVersions?.docs?.[0]?.version?._status || publishedDoc?._status || 'draft';
|
||||
|
||||
let versionsURL: string;
|
||||
let entity: SanitizedCollectionConfig | SanitizedGlobalConfig;
|
||||
|
||||
if (collection) {
|
||||
versionsURL = `${admin}/collections/${collection.slug}/${id}/versions`;
|
||||
entity = collection;
|
||||
}
|
||||
|
||||
if (global) {
|
||||
versionsURL = `${admin}/globals/${global.slug}/versions`;
|
||||
entity = global;
|
||||
}
|
||||
|
||||
let initialVersionsCount = 0;
|
||||
|
||||
if (shouldIncrementVersionCount({ entity, versions, docStatus })) {
|
||||
initialVersionsCount = 1;
|
||||
}
|
||||
|
||||
const versionCount = (versions?.totalDocs || 0) + initialVersionsCount;
|
||||
const versionCount = versions?.totalDocs || 0;
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
|
||||
@@ -191,7 +191,7 @@ export const addFieldStatePromise = async ({
|
||||
id,
|
||||
operation,
|
||||
fields: field.fields,
|
||||
data: data?.[field.name],
|
||||
data: data?.[field.name] || {},
|
||||
fullData,
|
||||
parentPassesCondition: passesCondition,
|
||||
path: `${path}${field.name}.`,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import format from 'date-fns/format';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useConfig } from '../../utilities/Config';
|
||||
import usePayloadAPI from '../../../hooks/usePayloadAPI';
|
||||
import Eyebrow from '../../elements/Eyebrow';
|
||||
import { LoadingOverlayToggle } from '../../elements/Loading';
|
||||
import { useStepNav } from '../../elements/StepNav';
|
||||
import { StepNavItem } from '../../elements/StepNav/types';
|
||||
import Meta from '../../utilities/Meta';
|
||||
@@ -15,20 +15,15 @@ import Table from '../../elements/Table';
|
||||
import Paginator from '../../elements/Paginator';
|
||||
import PerPage from '../../elements/PerPage';
|
||||
import { useSearchParams } from '../../utilities/SearchParams';
|
||||
import { Banner, Pill } from '../..';
|
||||
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
|
||||
import { SanitizedGlobalConfig } from '../../../../globals/config/types';
|
||||
import { shouldIncrementVersionCount } from '../../../../versions/shouldIncrementVersionCount';
|
||||
import { Gutter } from '../../elements/Gutter';
|
||||
import { getTranslation } from '../../../../utilities/getTranslation';
|
||||
import { LoadingOverlayToggle } from '../../elements/Loading';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'versions';
|
||||
|
||||
const Versions: React.FC<Props> = ({ collection, global }) => {
|
||||
const { serverURL, routes: { admin, api }, admin: { dateFormat } } = useConfig();
|
||||
const { serverURL, routes: { admin, api } } = useConfig();
|
||||
const { setStepNav } = useStepNav();
|
||||
const { params: { id } } = useRouteMatch<{ id: string }>();
|
||||
const { t, i18n } = useTranslation('version');
|
||||
@@ -39,14 +34,12 @@ const Versions: React.FC<Props> = ({ collection, global }) => {
|
||||
let docURL: string;
|
||||
let entityLabel: string;
|
||||
let slug: string;
|
||||
let entity: SanitizedCollectionConfig | SanitizedGlobalConfig;
|
||||
let editURL: string;
|
||||
|
||||
if (collection) {
|
||||
({ slug } = collection);
|
||||
docURL = `${serverURL}${api}/${slug}/${id}`;
|
||||
entityLabel = getTranslation(collection.labels.singular, i18n);
|
||||
entity = collection;
|
||||
editURL = `${admin}/collections/${collection.slug}/${id}`;
|
||||
}
|
||||
|
||||
@@ -54,13 +47,12 @@ const Versions: React.FC<Props> = ({ collection, global }) => {
|
||||
({ slug } = global);
|
||||
docURL = `${serverURL}${api}/globals/${slug}`;
|
||||
entityLabel = getTranslation(global.label, i18n);
|
||||
entity = global;
|
||||
editURL = `${admin}/globals/${global.slug}`;
|
||||
}
|
||||
|
||||
const useAsTitle = collection?.admin?.useAsTitle || 'id';
|
||||
const [{ data: doc }] = usePayloadAPI(docURL, { initialParams: { draft: 'true' } });
|
||||
const [{ data: versionsData, isLoading: isLoadingData }, { setParams }] = usePayloadAPI(fetchURL);
|
||||
const [{ data: versionsData, isLoading: isLoadingVersions }, { setParams }] = usePayloadAPI(fetchURL);
|
||||
|
||||
useEffect(() => {
|
||||
let nav: StepNavItem[] = [];
|
||||
@@ -164,17 +156,12 @@ const Versions: React.FC<Props> = ({ collection, global }) => {
|
||||
useIDLabel = false;
|
||||
}
|
||||
|
||||
const docStatus = doc?._status;
|
||||
const docUpdatedAt = doc?.updatedAt;
|
||||
const showParentDoc = versionsData?.page === 1 && shouldIncrementVersionCount({ entity, docStatus, versions: versionsData });
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<LoadingOverlayToggle
|
||||
show={isLoadingData}
|
||||
show={isLoadingVersions}
|
||||
name="versions"
|
||||
/>
|
||||
|
||||
<div className={baseClass}>
|
||||
<Meta
|
||||
title={metaTitle}
|
||||
@@ -194,26 +181,6 @@ const Versions: React.FC<Props> = ({ collection, global }) => {
|
||||
)}
|
||||
</header>
|
||||
|
||||
{showParentDoc && (
|
||||
<Banner
|
||||
type={docStatus === 'published' ? 'success' : undefined}
|
||||
className={`${baseClass}__parent-doc`}
|
||||
>
|
||||
{t('currentDocumentStatus', { docStatus })}
|
||||
-
|
||||
{' '}
|
||||
{format(new Date(docUpdatedAt), dateFormat)}
|
||||
<div className={`${baseClass}__parent-doc-pills`}>
|
||||
|
||||
<Pill
|
||||
pillStyle="white"
|
||||
to={editURL}
|
||||
>
|
||||
{t('general:edit')}
|
||||
</Pill>
|
||||
</div>
|
||||
</Banner>
|
||||
)}
|
||||
{versionsData?.totalDocs > 0 && (
|
||||
<React.Fragment>
|
||||
<Table
|
||||
|
||||
@@ -52,13 +52,13 @@ const RelationshipCell = (props) => {
|
||||
{values.map(({ relationTo, value }, i) => {
|
||||
const document = documents[relationTo][value];
|
||||
const relatedCollection = collections.find(({ slug }) => slug === relationTo);
|
||||
const label = document?.[relatedCollection.admin.useAsTitle] ? document[relatedCollection.admin.useAsTitle] : `${t('untitled')} - ID: ${value}`;
|
||||
|
||||
return (
|
||||
<React.Fragment key={i}>
|
||||
{document === false && `${t('untitled')} - ID: ${value}`}
|
||||
{document === null && `${t('loading')}...`}
|
||||
{document && (
|
||||
document[relatedCollection.admin.useAsTitle] ? document[relatedCollection.admin.useAsTitle] : `${t('untitled')} - ID: ${value}`
|
||||
)}
|
||||
{document && label}
|
||||
{values.length > i + 1 && ', '}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import formatName from '../../../graphql/utilities/formatName';
|
||||
import access from '../../operations/access';
|
||||
import { Payload } from '../../..';
|
||||
import { Payload } from '../../../payload';
|
||||
|
||||
const formatConfigNames = (results, configs) => {
|
||||
const formattedResults = { ...results };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import passport from 'passport';
|
||||
import AnonymousStrategy from 'passport-anonymous';
|
||||
import { Payload } from '../index';
|
||||
import { Payload } from '../payload';
|
||||
import jwtStrategy from './strategies/jwt';
|
||||
|
||||
function initAuth(ctx: Payload): void {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import forgotPassword, { Result } from '../forgotPassword';
|
||||
import { Payload } from '../../..';
|
||||
import { Payload } from '../../../payload';
|
||||
import { getDataLoader } from '../../../collections/dataloader';
|
||||
import i18n from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
collection: string
|
||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
collection: T
|
||||
data: {
|
||||
email: string
|
||||
}
|
||||
@@ -15,7 +16,10 @@ export type Options = {
|
||||
req?: PayloadRequest
|
||||
}
|
||||
|
||||
async function localForgotPassword(payload: Payload, options: Options): Promise<Result> {
|
||||
async function localForgotPassword<T extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<Result> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
data,
|
||||
@@ -27,7 +31,7 @@ async function localForgotPassword(payload: Payload, options: Options): Promise<
|
||||
const collection = payload.collections[collectionSlug];
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
req.payloadAPI = 'local';
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Response } from 'express';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import login, { Result } from '../login';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { TypeWithID } from '../../../collections/config/types';
|
||||
import { Payload } from '../../..';
|
||||
import { Payload } from '../../../payload';
|
||||
import { getDataLoader } from '../../../collections/dataloader';
|
||||
import i18n from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
collection: string
|
||||
data: {
|
||||
export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
|
||||
collection: TSlug
|
||||
data: Omit<GeneratedTypes['collections'][TSlug], 'id'> & {
|
||||
email: string
|
||||
password: string
|
||||
}
|
||||
@@ -22,7 +22,10 @@ export type Options = {
|
||||
showHiddenFields?: boolean
|
||||
}
|
||||
|
||||
async function localLogin<T extends TypeWithID = any>(payload: Payload, options: Options): Promise<Result & { user: T }> {
|
||||
async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<TSlug>,
|
||||
): Promise<Result & { user: GeneratedTypes['collections'][TSlug] }> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
req = {} as PayloadRequest,
|
||||
@@ -38,7 +41,7 @@ async function localLogin<T extends TypeWithID = any>(payload: Payload, options:
|
||||
const collection = payload.collections[collectionSlug];
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
req.payloadAPI = 'local';
|
||||
@@ -63,7 +66,7 @@ async function localLogin<T extends TypeWithID = any>(payload: Payload, options:
|
||||
if (locale) args.req.locale = locale;
|
||||
if (fallbackLocale) args.req.fallbackLocale = fallbackLocale;
|
||||
|
||||
return login(args);
|
||||
return login<TSlug>(args);
|
||||
}
|
||||
|
||||
export default localLogin;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Payload } from '../../..';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Payload } from '../../../payload';
|
||||
import resetPassword, { Result } from '../resetPassword';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { getDataLoader } from '../../../collections/dataloader';
|
||||
import i18n from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
collection: string
|
||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
collection: T
|
||||
data: {
|
||||
token: string
|
||||
password: string
|
||||
@@ -15,7 +16,10 @@ export type Options = {
|
||||
req?: PayloadRequest
|
||||
}
|
||||
|
||||
async function localResetPassword(payload: Payload, options: Options): Promise<Result> {
|
||||
async function localResetPassword<T extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<Result> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
data,
|
||||
@@ -26,7 +30,7 @@ async function localResetPassword(payload: Payload, options: Options): Promise<R
|
||||
const collection = payload.collections[collectionSlug];
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
req.payload = payload;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { Payload } from '../../..';
|
||||
import { Payload } from '../../../payload';
|
||||
import unlock from '../unlock';
|
||||
import { getDataLoader } from '../../../collections/dataloader';
|
||||
import i18n from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
collection: string
|
||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
collection: T
|
||||
data: {
|
||||
email
|
||||
}
|
||||
@@ -14,7 +15,10 @@ export type Options = {
|
||||
overrideAccess: boolean
|
||||
}
|
||||
|
||||
async function localUnlock(payload: Payload, options: Options): Promise<boolean> {
|
||||
async function localUnlock<T extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<boolean> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
data,
|
||||
@@ -25,7 +29,7 @@ async function localUnlock(payload: Payload, options: Options): Promise<boolean>
|
||||
const collection = payload.collections[collectionSlug];
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
req.payload = payload;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { APIError } from '../../../errors';
|
||||
import { Payload } from '../../../index';
|
||||
import { Payload } from '../../../payload';
|
||||
import verifyEmail from '../verifyEmail';
|
||||
|
||||
export type Options = {
|
||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
token: string,
|
||||
collection: string
|
||||
collection: T
|
||||
}
|
||||
|
||||
async function localVerifyEmail(payload: Payload, options: Options): Promise<boolean> {
|
||||
async function localVerifyEmail<T extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<boolean> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
token,
|
||||
@@ -16,7 +20,7 @@ async function localVerifyEmail(payload: Payload, options: Options): Promise<boo
|
||||
const collection = payload.collections[collectionSlug];
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
return verifyEmail({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { CookieOptions, Response } from 'express';
|
||||
import { AuthenticationError, LockedAuth } from '../../errors';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
@@ -17,9 +18,9 @@ export type Result = {
|
||||
exp?: number,
|
||||
}
|
||||
|
||||
export type Arguments = {
|
||||
export type Arguments<T extends { [field: string | number | symbol]: unknown }> = {
|
||||
collection: Collection,
|
||||
data: {
|
||||
data: Omit<T, 'id'> & {
|
||||
email: string
|
||||
password: string
|
||||
}
|
||||
@@ -30,7 +31,9 @@ export type Arguments = {
|
||||
showHiddenFields?: boolean
|
||||
}
|
||||
|
||||
async function login<T>(incomingArgs: Arguments): Promise<Result & { user: T}> {
|
||||
async function login<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
incomingArgs: Arguments<GeneratedTypes['collections'][TSlug]>,
|
||||
): Promise<Result & { user: GeneratedTypes['collections'][TSlug] }> {
|
||||
let args = incomingArgs;
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Response } from 'express';
|
||||
import { Document } from '../../types';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Forbidden } from '../../errors';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import { Collection, TypeWithID } from '../../collections/config/types';
|
||||
|
||||
export type Arguments = {
|
||||
collection: Collection
|
||||
data: {
|
||||
data: Omit<TypeWithID, 'id'> & {
|
||||
email: string
|
||||
password: string
|
||||
}
|
||||
@@ -14,12 +14,14 @@ export type Arguments = {
|
||||
res: Response
|
||||
}
|
||||
|
||||
export type Result = {
|
||||
export type Result<T> = {
|
||||
message: string,
|
||||
user: Document
|
||||
user: T
|
||||
}
|
||||
|
||||
async function registerFirstUser(args: Arguments): Promise<Result> {
|
||||
async function registerFirstUser<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
args: Arguments,
|
||||
): Promise<Result<GeneratedTypes['collections'][TSlug]>> {
|
||||
const {
|
||||
collection: {
|
||||
Model,
|
||||
@@ -45,9 +47,9 @@ async function registerFirstUser(args: Arguments): Promise<Result> {
|
||||
// Register first user
|
||||
// /////////////////////////////////////
|
||||
|
||||
const result = await payload.create<TypeWithID>({
|
||||
const result = await payload.create<TSlug>({
|
||||
req,
|
||||
collection: slug,
|
||||
collection: slug as TSlug,
|
||||
data,
|
||||
overrideAccess: true,
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import { PayloadRequest } from '../../express/types';
|
||||
|
||||
export type Result = {
|
||||
token: string
|
||||
user: UserDocument
|
||||
user: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type Arguments = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Payload } from '..';
|
||||
import { Payload } from '../payload';
|
||||
import { PayloadRequest } from '../express/types';
|
||||
import { SanitizedConfig, EmailOptions } from '../config/types';
|
||||
import { Collection } from '../collections/config/types';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import PassportAPIKey from 'passport-headerapikey';
|
||||
import crypto from 'crypto';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import { Payload } from '../..';
|
||||
import { Payload } from '../../payload';
|
||||
import find from '../../collections/operations/find';
|
||||
|
||||
export default (payload: Payload, { Model, config }): PassportAPIKey => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import url from 'url';
|
||||
import passportJwt, { StrategyOptions } from 'passport-jwt';
|
||||
import { Strategy as PassportStrategy } from 'passport-strategy';
|
||||
import { Payload } from '../..';
|
||||
import { Payload } from '../../payload';
|
||||
import getExtractJWT from '../getExtractJWT';
|
||||
|
||||
const JwtStrategy = passportJwt.Strategy;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Strategy } from 'passport';
|
||||
import { DeepRequired } from 'ts-essentials';
|
||||
import { PayloadRequest } from '../express/types';
|
||||
import { Where, PayloadMongooseDocument } from '../types';
|
||||
import { Payload } from '..';
|
||||
import { Payload } from '../payload';
|
||||
|
||||
export type Permission = {
|
||||
permission: boolean
|
||||
|
||||
@@ -411,8 +411,30 @@ function entityToJsonSchema(config: SanitizedConfig, incomingEntity: SanitizedCo
|
||||
};
|
||||
}
|
||||
|
||||
function generateEntityObject(config: SanitizedConfig, type: 'collections' | 'globals'): JSONSchema4 {
|
||||
return {
|
||||
type: 'object',
|
||||
properties: Object.fromEntries(config[type].map(({ slug }) => [
|
||||
slug,
|
||||
{
|
||||
$ref: `#/definitions/${slug}`,
|
||||
},
|
||||
])),
|
||||
required: config[type].map(({ slug }) => slug),
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
|
||||
function configToJsonSchema(config: SanitizedConfig): JSONSchema4 {
|
||||
return {
|
||||
title: 'Config',
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
collections: generateEntityObject(config, 'collections'),
|
||||
globals: generateEntityObject(config, 'globals'),
|
||||
},
|
||||
required: ['collections', 'globals'],
|
||||
definitions: Object.fromEntries(
|
||||
[
|
||||
...config.globals.map((global) => [
|
||||
@@ -425,7 +447,6 @@ function configToJsonSchema(config: SanitizedConfig): JSONSchema4 {
|
||||
]),
|
||||
],
|
||||
),
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -439,7 +460,6 @@ export function generateTypes(): void {
|
||||
const jsonSchema = configToJsonSchema(config);
|
||||
|
||||
compile(jsonSchema, 'Config', {
|
||||
unreachableDefinitions: true,
|
||||
bannerComment: '/* tslint:disable */\n/**\n* This file was automatically generated by Payload CMS.\n* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,\n* and re-run `payload generate:types` to regenerate this file.\n*/',
|
||||
style: {
|
||||
singleQuote: true,
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import minimist from 'minimist';
|
||||
import swcRegister from '@swc/register';
|
||||
import { generateTypes } from './generateTypes';
|
||||
import { generateGraphQLSchema } from './generateGraphQLSchema';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - bad @swc/register types
|
||||
swcRegister({
|
||||
sourceMaps: 'inline',
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
tsx: true,
|
||||
},
|
||||
},
|
||||
module: {
|
||||
type: 'commonjs',
|
||||
},
|
||||
});
|
||||
|
||||
const { build } = require('./build');
|
||||
|
||||
const args = minimist(process.argv.slice(2));
|
||||
|
||||
@@ -323,10 +323,12 @@ export type AuthCollection = {
|
||||
|
||||
export type TypeWithID = {
|
||||
id: string | number
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type TypeWithTimestamps = {
|
||||
id: string | number
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import DataLoader, { BatchLoadFn } from 'dataloader';
|
||||
import { PayloadRequest } from '../express/types';
|
||||
import { TypeWithID } from '../globals/config/types';
|
||||
import { TypeWithID } from './config/types';
|
||||
import { isValidID } from '../utilities/isValidID';
|
||||
import { getIDType } from '../utilities/getIDType';
|
||||
import { fieldAffectsData } from '../fields/config/types';
|
||||
|
||||
@@ -28,7 +28,7 @@ import resetPassword from '../../auth/graphql/resolvers/resetPassword';
|
||||
import verifyEmail from '../../auth/graphql/resolvers/verifyEmail';
|
||||
import unlock from '../../auth/graphql/resolvers/unlock';
|
||||
import refresh from '../../auth/graphql/resolvers/refresh';
|
||||
import { Payload } from '../..';
|
||||
import { Payload } from '../../payload';
|
||||
import { Field, fieldAffectsData } from '../../fields/config/types';
|
||||
import buildObjectType, { ObjectTypeConfig } from '../../graphql/schema/buildObjectType';
|
||||
import buildWhereInputType from '../../graphql/schema/buildWhereInputType';
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { Response } from 'express';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { Collection } from '../../config/types';
|
||||
import create from '../../operations/create';
|
||||
|
||||
export type Resolver = (_: unknown, args: {
|
||||
data: Record<string, unknown>,
|
||||
export type Resolver<TSlug extends keyof GeneratedTypes['collections']> = (_: unknown, args: {
|
||||
data: Omit<GeneratedTypes['collections'][TSlug], 'id'>,
|
||||
locale?: string
|
||||
draft: boolean
|
||||
},
|
||||
@@ -13,9 +14,11 @@ export type Resolver = (_: unknown, args: {
|
||||
req: PayloadRequest,
|
||||
res: Response
|
||||
}
|
||||
) => Promise<Document>
|
||||
) => Promise<GeneratedTypes['collections'][TSlug]>
|
||||
|
||||
export default function createResolver(collection: Collection): Resolver {
|
||||
export default function createResolver<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
collection: Collection,
|
||||
): Resolver<TSlug> {
|
||||
return async function resolver(_, args, context) {
|
||||
if (args.locale) {
|
||||
context.req.locale = args.locale;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Response } from 'express';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { Collection } from '../../config/types';
|
||||
import deleteOperation from '../../operations/delete';
|
||||
|
||||
export type Resolver = (
|
||||
export type Resolver<TSlug extends keyof GeneratedTypes['collections']> = (
|
||||
_: unknown,
|
||||
args: {
|
||||
locale?: string
|
||||
@@ -14,9 +15,11 @@ export type Resolver = (
|
||||
req: PayloadRequest,
|
||||
res: Response
|
||||
}
|
||||
) => Promise<Document>
|
||||
) => Promise<GeneratedTypes['collections'][TSlug]>
|
||||
|
||||
export default function getDeleteResolver(collection: Collection): Resolver {
|
||||
export default function getDeleteResolver<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
collection: Collection,
|
||||
): Resolver<TSlug> {
|
||||
async function resolver(_, args, context) {
|
||||
if (args.locale) context.req.locale = args.locale;
|
||||
if (args.fallbackLocale) context.req.fallbackLocale = args.fallbackLocale;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Config as SchemaConfig } from 'payload/generated-types';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { Collection } from '../../config/types';
|
||||
import findByID from '../../operations/findByID';
|
||||
|
||||
export type Resolver = (_: unknown, args: {
|
||||
export type Resolver<T> = (_: unknown, args: {
|
||||
locale?: string
|
||||
draft: boolean
|
||||
id: string
|
||||
@@ -12,9 +13,9 @@ export type Resolver = (_: unknown, args: {
|
||||
req: PayloadRequest,
|
||||
res: Response
|
||||
}
|
||||
) => Promise<Document>
|
||||
) => Promise<T>
|
||||
|
||||
export default function findByIDResolver(collection: Collection): Resolver {
|
||||
export default function findByIDResolver<T extends keyof SchemaConfig['collections']>(collection: Collection): Resolver<SchemaConfig['collections'][T]> {
|
||||
return async function resolver(_, args, context) {
|
||||
const { req } = context;
|
||||
if (args.locale) req.locale = args.locale;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { Response } from 'express';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Collection } from '../../config/types';
|
||||
import update from '../../operations/update';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
|
||||
export type Resolver = (_: unknown, args: {
|
||||
export type Resolver<TSlug extends keyof GeneratedTypes['collections']> = (_: unknown, args: {
|
||||
id: string | number
|
||||
data: Record<string, unknown>,
|
||||
data: GeneratedTypes['collections'][TSlug]
|
||||
locale?: string
|
||||
draft: boolean
|
||||
autosave: boolean
|
||||
@@ -15,9 +16,11 @@ export type Resolver = (_: unknown, args: {
|
||||
req: PayloadRequest,
|
||||
res: Response
|
||||
}
|
||||
) => Promise<Document>
|
||||
) => Promise<GeneratedTypes['collections'][TSlug]>
|
||||
|
||||
export default function updateResolver(collection: Collection): Resolver {
|
||||
export default function updateResolver<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
collection: Collection,
|
||||
): Resolver<TSlug> {
|
||||
async function resolver(_, args, context) {
|
||||
if (args.locale) context.req.locale = args.locale;
|
||||
if (args.fallbackLocale) context.req.fallbackLocale = args.fallbackLocale;
|
||||
@@ -32,7 +35,7 @@ export default function updateResolver(collection: Collection): Resolver {
|
||||
autosave: args.autosave,
|
||||
};
|
||||
|
||||
const result = await update(options);
|
||||
const result = await update<TSlug>(options);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
41
src/collections/initHTTP.ts
Normal file
41
src/collections/initHTTP.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import express from 'express';
|
||||
import passport from 'passport';
|
||||
import apiKeyStrategy from '../auth/strategies/apiKey';
|
||||
import bindCollectionMiddleware from './bindCollection';
|
||||
import { SanitizedCollectionConfig } from './config/types';
|
||||
import mountEndpoints from '../express/mountEndpoints';
|
||||
import buildEndpoints from './buildEndpoints';
|
||||
import { Payload } from '../payload';
|
||||
|
||||
export default function initCollectionsHTTP(ctx: Payload): void {
|
||||
ctx.config.collections = ctx.config.collections.map((collection: SanitizedCollectionConfig) => {
|
||||
const formattedCollection = collection;
|
||||
|
||||
const router = express.Router();
|
||||
const { slug } = collection;
|
||||
|
||||
router.all('*', bindCollectionMiddleware(ctx.collections[formattedCollection.slug]));
|
||||
|
||||
if (collection.auth) {
|
||||
const AuthCollection = ctx.collections[formattedCollection.slug];
|
||||
|
||||
if (collection.auth.useAPIKey) {
|
||||
passport.use(`${AuthCollection.config.slug}-api-key`, apiKeyStrategy(ctx, AuthCollection));
|
||||
}
|
||||
|
||||
if (Array.isArray(collection.auth.strategies)) {
|
||||
collection.auth.strategies.forEach(({ name, strategy }, index) => {
|
||||
const passportStrategy = typeof strategy === 'object' ? strategy : strategy(ctx);
|
||||
passport.use(`${AuthCollection.config.slug}-${name ?? index}`, passportStrategy);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const endpoints = buildEndpoints(collection);
|
||||
mountEndpoints(ctx.express, router, endpoints);
|
||||
|
||||
ctx.router.use(`/${slug}`, router);
|
||||
|
||||
return formattedCollection;
|
||||
});
|
||||
}
|
||||
@@ -1,21 +1,16 @@
|
||||
import mongoose, { UpdateAggregationStage, UpdateQuery } from 'mongoose';
|
||||
import paginate from 'mongoose-paginate-v2';
|
||||
import express from 'express';
|
||||
import passport from 'passport';
|
||||
import passportLocalMongoose from 'passport-local-mongoose';
|
||||
import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2';
|
||||
import { buildVersionCollectionFields } from '../versions/buildCollectionFields';
|
||||
import buildQueryPlugin from '../mongoose/buildQuery';
|
||||
import apiKeyStrategy from '../auth/strategies/apiKey';
|
||||
import buildCollectionSchema from './buildSchema';
|
||||
import buildSchema from '../mongoose/buildSchema';
|
||||
import bindCollectionMiddleware from './bindCollection';
|
||||
import { CollectionModel, SanitizedCollectionConfig } from './config/types';
|
||||
import { Payload } from '../index';
|
||||
import { Payload } from '../payload';
|
||||
import { getVersionsModelName } from '../versions/getVersionsModelName';
|
||||
import mountEndpoints from '../express/mountEndpoints';
|
||||
import buildEndpoints from './buildEndpoints';
|
||||
|
||||
export default function registerCollections(ctx: Payload): void {
|
||||
export default function initCollectionsLocal(ctx: Payload): void {
|
||||
ctx.config.collections = ctx.config.collections.map((collection: SanitizedCollectionConfig) => {
|
||||
const formattedCollection = collection;
|
||||
|
||||
@@ -74,7 +69,7 @@ export default function registerCollections(ctx: Payload): void {
|
||||
disableUnique: true,
|
||||
draftsEnabled: true,
|
||||
options: {
|
||||
timestamps: true,
|
||||
timestamps: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -82,6 +77,10 @@ export default function registerCollections(ctx: Payload): void {
|
||||
versionSchema.plugin(paginate, { useEstimatedCount: true })
|
||||
.plugin(buildQueryPlugin);
|
||||
|
||||
if (collection.versions?.drafts) {
|
||||
versionSchema.plugin(mongooseAggregatePaginate);
|
||||
}
|
||||
|
||||
ctx.versions[collection.slug] = mongoose.model(versionModelName, versionSchema) as CollectionModel;
|
||||
}
|
||||
|
||||
@@ -91,34 +90,6 @@ export default function registerCollections(ctx: Payload): void {
|
||||
config: formattedCollection,
|
||||
};
|
||||
|
||||
// If not local, open routes
|
||||
if (!ctx.local) {
|
||||
const router = express.Router();
|
||||
const { slug } = collection;
|
||||
|
||||
router.all('*', bindCollectionMiddleware(ctx.collections[formattedCollection.slug]));
|
||||
|
||||
if (collection.auth) {
|
||||
const AuthCollection = ctx.collections[formattedCollection.slug];
|
||||
|
||||
if (collection.auth.useAPIKey) {
|
||||
passport.use(`${AuthCollection.config.slug}-api-key`, apiKeyStrategy(ctx, AuthCollection));
|
||||
}
|
||||
|
||||
if (Array.isArray(collection.auth.strategies)) {
|
||||
collection.auth.strategies.forEach(({ name, strategy }, index) => {
|
||||
const passportStrategy = typeof strategy === 'object' ? strategy : strategy(ctx);
|
||||
passport.use(`${AuthCollection.config.slug}-${name ?? index}`, passportStrategy);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const endpoints = buildEndpoints(collection);
|
||||
mountEndpoints(ctx.express, router, endpoints);
|
||||
|
||||
ctx.router.use(`/${slug}`, router);
|
||||
}
|
||||
|
||||
return formattedCollection;
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
|
||||
@@ -16,20 +16,24 @@ import { beforeValidate } from '../../fields/hooks/beforeValidate';
|
||||
import { afterChange } from '../../fields/hooks/afterChange';
|
||||
import { afterRead } from '../../fields/hooks/afterRead';
|
||||
import { generateFileData } from '../../uploads/generateFileData';
|
||||
import { saveVersion } from '../../versions/saveVersion';
|
||||
|
||||
export type Arguments = {
|
||||
export type Arguments<T extends { [field: string | number | symbol]: unknown }> = {
|
||||
collection: Collection
|
||||
req: PayloadRequest
|
||||
depth?: number
|
||||
disableVerificationEmail?: boolean
|
||||
overrideAccess?: boolean
|
||||
showHiddenFields?: boolean
|
||||
data: Record<string, unknown>
|
||||
data: Omit<T, 'id'>
|
||||
overwriteExistingFiles?: boolean
|
||||
draft?: boolean
|
||||
autosave?: boolean
|
||||
}
|
||||
|
||||
async function create(incomingArgs: Arguments): Promise<Document> {
|
||||
async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
incomingArgs: Arguments<GeneratedTypes['collections'][TSlug]>,
|
||||
): Promise<GeneratedTypes['collections'][TSlug]> {
|
||||
let args = incomingArgs;
|
||||
|
||||
// /////////////////////////////////////
|
||||
@@ -65,6 +69,7 @@ async function create(incomingArgs: Arguments): Promise<Document> {
|
||||
showHiddenFields,
|
||||
overwriteExistingFiles = false,
|
||||
draft = false,
|
||||
autosave = false,
|
||||
} = args;
|
||||
|
||||
let { data } = args;
|
||||
@@ -159,7 +164,7 @@ async function create(incomingArgs: Arguments): Promise<Document> {
|
||||
// beforeChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
const resultWithLocales = await beforeChange({
|
||||
const resultWithLocales = await beforeChange<Record<string, unknown>>({
|
||||
data,
|
||||
doc: {},
|
||||
docWithLocales: {},
|
||||
@@ -213,6 +218,23 @@ async function create(incomingArgs: Arguments): Promise<Document> {
|
||||
result = JSON.parse(result);
|
||||
result = sanitizeInternalFields(result);
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Create version
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (collectionConfig.versions) {
|
||||
await saveVersion({
|
||||
payload,
|
||||
collection: collectionConfig,
|
||||
req,
|
||||
id: result.id,
|
||||
docWithLocales: result,
|
||||
autosave,
|
||||
createdAt: result.createdAt,
|
||||
onCreate: true,
|
||||
});
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Send verification email if applicable
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { NotFound, Forbidden, ErrorDeletingFile } from '../../errors';
|
||||
@@ -11,6 +12,7 @@ import { hasWhereAccessResult } from '../../auth/types';
|
||||
import { FileData } from '../../uploads/types';
|
||||
import fileExists from '../../uploads/fileExists';
|
||||
import { afterRead } from '../../fields/hooks/afterRead';
|
||||
import { deleteCollectionVersions } from '../../versions/deleteCollectionVersions';
|
||||
|
||||
export type Arguments = {
|
||||
depth?: number
|
||||
@@ -21,7 +23,9 @@ export type Arguments = {
|
||||
showHiddenFields?: boolean
|
||||
}
|
||||
|
||||
async function deleteOperation(incomingArgs: Arguments): Promise<Document> {
|
||||
async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
incomingArgs: Arguments,
|
||||
): Promise<GeneratedTypes['collections'][TSlug]> {
|
||||
let args = incomingArgs;
|
||||
|
||||
// /////////////////////////////////////
|
||||
@@ -48,6 +52,7 @@ async function deleteOperation(incomingArgs: Arguments): Promise<Document> {
|
||||
req: {
|
||||
t,
|
||||
locale,
|
||||
payload,
|
||||
payload: {
|
||||
config,
|
||||
preferences,
|
||||
@@ -164,6 +169,18 @@ async function deleteOperation(incomingArgs: Arguments): Promise<Document> {
|
||||
}
|
||||
await preferences.Model.deleteMany({ key: `collection-${collectionConfig.slug}-${id}` });
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete versions
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (!collectionConfig.versions.retainDeleted) {
|
||||
deleteCollectionVersions({
|
||||
payload,
|
||||
id,
|
||||
slug: collectionConfig.slug,
|
||||
});
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterDelete - Collection
|
||||
// /////////////////////////////////////
|
||||
@@ -174,7 +191,6 @@ async function deleteOperation(incomingArgs: Arguments): Promise<Document> {
|
||||
result = await hook({ req, id, doc: result }) || result;
|
||||
}, Promise.resolve());
|
||||
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -9,7 +9,7 @@ import flattenWhereConstraints from '../../utilities/flattenWhereConstraints';
|
||||
import { buildSortParam } from '../../mongoose/buildSortParam';
|
||||
import { AccessResult } from '../../config/types';
|
||||
import { afterRead } from '../../fields/hooks/afterRead';
|
||||
import { mergeDrafts } from '../../versions/drafts/mergeDrafts';
|
||||
import { queryDrafts } from '../../versions/drafts/queryDrafts';
|
||||
|
||||
export type Arguments = {
|
||||
collection: Collection
|
||||
@@ -28,7 +28,9 @@ export type Arguments = {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async function find<T extends TypeWithID = any>(incomingArgs: Arguments): Promise<PaginatedDocs<T>> {
|
||||
async function find<T extends TypeWithID>(
|
||||
incomingArgs: Arguments,
|
||||
): Promise<PaginatedDocs<T>> {
|
||||
let args = incomingArgs;
|
||||
|
||||
// /////////////////////////////////////
|
||||
@@ -160,13 +162,12 @@ async function find<T extends TypeWithID = any>(incomingArgs: Arguments): Promis
|
||||
};
|
||||
|
||||
if (collectionConfig.versions?.drafts && draftsEnabled) {
|
||||
result = await mergeDrafts({
|
||||
result = await queryDrafts<T>({
|
||||
accessResult,
|
||||
collection,
|
||||
locale,
|
||||
paginationOptions,
|
||||
payload,
|
||||
query,
|
||||
where,
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -22,8 +22,9 @@ export type Arguments = {
|
||||
draft?: boolean
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async function findByID<T extends TypeWithID = any>(incomingArgs: Arguments): Promise<T> {
|
||||
async function findByID<T extends TypeWithID>(
|
||||
incomingArgs: Arguments,
|
||||
): Promise<T> {
|
||||
let args = incomingArgs;
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -23,7 +23,9 @@ export type Arguments = {
|
||||
showHiddenFields?: boolean
|
||||
}
|
||||
|
||||
async function findVersions<T extends TypeWithVersion<T> = any>(args: Arguments): Promise<PaginatedDocs<T>> {
|
||||
async function findVersions<T extends TypeWithVersion<T>>(
|
||||
args: Arguments,
|
||||
): Promise<PaginatedDocs<T>> {
|
||||
const {
|
||||
where,
|
||||
page,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { UploadedFile } from 'express-fileupload';
|
||||
import { Payload } from '../../..';
|
||||
import { Payload } from '../../../payload';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { Document } from '../../../types';
|
||||
import getFileByPath from '../../../uploads/getFileByPath';
|
||||
@@ -9,9 +10,9 @@ import { File } from '../../../uploads/types';
|
||||
import i18n from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options<T> = {
|
||||
collection: string
|
||||
data: Record<string, unknown>
|
||||
export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
|
||||
collection: TSlug
|
||||
data: Omit<GeneratedTypes['collections'][TSlug], 'id'>
|
||||
depth?: number
|
||||
locale?: string
|
||||
fallbackLocale?: string
|
||||
@@ -26,7 +27,10 @@ export type Options<T> = {
|
||||
draft?: boolean
|
||||
}
|
||||
|
||||
export default async function createLocal<T = any>(payload: Payload, options: Options<T>): Promise<T> {
|
||||
export default async function createLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<TSlug>,
|
||||
): Promise<GeneratedTypes['collections'][TSlug]> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
@@ -48,7 +52,7 @@ export default async function createLocal<T = any>(payload: Payload, options: Op
|
||||
const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null;
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
req.payloadAPI = 'local';
|
||||
@@ -65,7 +69,7 @@ export default async function createLocal<T = any>(payload: Payload, options: Op
|
||||
if (!req.t) req.t = req.i18n.t;
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);
|
||||
|
||||
return create({
|
||||
return create<TSlug>({
|
||||
depth,
|
||||
data,
|
||||
collection,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { TypeWithID } from '../../config/types';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Document } from '../../../types';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { Payload } from '../../../index';
|
||||
import { Payload } from '../../../payload';
|
||||
import deleteOperation from '../delete';
|
||||
import { getDataLoader } from '../../dataloader';
|
||||
import i18n from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
collection: string
|
||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
collection: T
|
||||
id: string
|
||||
depth?: number
|
||||
locale?: string
|
||||
@@ -18,7 +18,10 @@ export type Options = {
|
||||
showHiddenFields?: boolean
|
||||
}
|
||||
|
||||
export default async function deleteLocal<T extends TypeWithID = any>(payload: Payload, options: Options): Promise<T> {
|
||||
export default async function deleteLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<TSlug>,
|
||||
): Promise<GeneratedTypes['collections'][TSlug]> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
@@ -35,7 +38,7 @@ export default async function deleteLocal<T extends TypeWithID = any>(payload: P
|
||||
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
const req = {
|
||||
@@ -50,7 +53,7 @@ export default async function deleteLocal<T extends TypeWithID = any>(payload: P
|
||||
if (!req.t) req.t = req.i18n.t;
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);
|
||||
|
||||
return deleteOperation({
|
||||
return deleteOperation<TSlug>({
|
||||
depth,
|
||||
id,
|
||||
collection,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { TypeWithID } from '../../config/types';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { PaginatedDocs } from '../../../mongoose/types';
|
||||
import { Document, Where } from '../../../types';
|
||||
import { Payload } from '../../..';
|
||||
import { Payload } from '../../../payload';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import find from '../find';
|
||||
import { getDataLoader } from '../../dataloader';
|
||||
import i18n from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
collection: string
|
||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
collection: T
|
||||
depth?: number
|
||||
currentDepth?: number
|
||||
page?: number
|
||||
@@ -27,8 +27,10 @@ export type Options = {
|
||||
req?: PayloadRequest
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export default async function findLocal<T extends TypeWithID = any>(payload: Payload, options: Options): Promise<PaginatedDocs<T>> {
|
||||
export default async function findLocal<T extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<PaginatedDocs<GeneratedTypes['collections'][T]>> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
@@ -52,7 +54,7 @@ export default async function findLocal<T extends TypeWithID = any>(payload: Pay
|
||||
const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null;
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
req.payloadAPI = 'local';
|
||||
@@ -66,7 +68,7 @@ export default async function findLocal<T extends TypeWithID = any>(payload: Pay
|
||||
|
||||
if (typeof user !== 'undefined') req.user = user;
|
||||
|
||||
return find({
|
||||
return find<GeneratedTypes['collections'][T]>({
|
||||
depth,
|
||||
currentDepth,
|
||||
sort,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { TypeWithID } from '../../config/types';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { Document } from '../../../types';
|
||||
import findByID from '../findByID';
|
||||
import { Payload } from '../../..';
|
||||
import { Payload } from '../../../payload';
|
||||
import { getDataLoader } from '../../dataloader';
|
||||
import i18n from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
collection: string
|
||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
collection: T
|
||||
id: string
|
||||
depth?: number
|
||||
currentDepth?: number
|
||||
@@ -22,8 +22,10 @@ export type Options = {
|
||||
draft?: boolean
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export default async function findByIDLocal<T extends TypeWithID = any>(payload: Payload, options: Options): Promise<T> {
|
||||
export default async function findByIDLocal<T extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<GeneratedTypes['collections'][T]> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
@@ -43,7 +45,7 @@ export default async function findByIDLocal<T extends TypeWithID = any>(payload:
|
||||
const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null;
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
req.payloadAPI = 'local';
|
||||
@@ -57,7 +59,7 @@ export default async function findByIDLocal<T extends TypeWithID = any>(payload:
|
||||
if (!req.t) req.t = req.i18n.t;
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);
|
||||
|
||||
return findByID({
|
||||
return findByID<GeneratedTypes['collections'][T]>({
|
||||
depth,
|
||||
currentDepth,
|
||||
id,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Payload } from '../../..';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Payload } from '../../../payload';
|
||||
import { Document } from '../../../types';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { TypeWithVersion } from '../../../versions/types';
|
||||
@@ -7,8 +8,8 @@ import { getDataLoader } from '../../dataloader';
|
||||
import i18n from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
collection: string
|
||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
collection: T
|
||||
id: string
|
||||
depth?: number
|
||||
locale?: string
|
||||
@@ -20,7 +21,10 @@ export type Options = {
|
||||
req?: PayloadRequest
|
||||
}
|
||||
|
||||
export default async function findVersionByIDLocal<T extends TypeWithVersion<T> = any>(payload: Payload, options: Options): Promise<T> {
|
||||
export default async function findVersionByIDLocal<T extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<TypeWithVersion<GeneratedTypes['collections'][T]>> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
@@ -37,7 +41,7 @@ export default async function findVersionByIDLocal<T extends TypeWithVersion<T>
|
||||
const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null;
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
req.payloadAPI = 'local';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Payload } from '../../..';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Payload } from '../../../payload';
|
||||
import { Document, Where } from '../../../types';
|
||||
import { PaginatedDocs } from '../../../mongoose/types';
|
||||
import { TypeWithVersion } from '../../../versions/types';
|
||||
@@ -8,8 +9,8 @@ import { getDataLoader } from '../../dataloader';
|
||||
import i18nInit from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
collection: string
|
||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
collection: T
|
||||
depth?: number
|
||||
page?: number
|
||||
limit?: number
|
||||
@@ -22,7 +23,10 @@ export type Options = {
|
||||
where?: Where
|
||||
}
|
||||
|
||||
export default async function findVersionsLocal<T extends TypeWithVersion<T> = any>(payload: Payload, options: Options): Promise<PaginatedDocs<T>> {
|
||||
export default async function findVersionsLocal<T extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<PaginatedDocs<TypeWithVersion<GeneratedTypes['collections'][T]>>> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
@@ -41,7 +45,7 @@ export default async function findVersionsLocal<T extends TypeWithVersion<T> = a
|
||||
const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null;
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
const i18n = i18nInit(payload.config.i18n);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Payload } from '../../..';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Payload } from '../../../payload';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { Document } from '../../../types';
|
||||
import { TypeWithVersion } from '../../../versions/types';
|
||||
import { getDataLoader } from '../../dataloader';
|
||||
import restoreVersion from '../restoreVersion';
|
||||
import i18nInit from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
collection: string
|
||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
collection: T
|
||||
id: string
|
||||
depth?: number
|
||||
locale?: string
|
||||
@@ -18,7 +18,10 @@ export type Options = {
|
||||
showHiddenFields?: boolean
|
||||
}
|
||||
|
||||
export default async function restoreVersionLocal<T extends TypeWithVersion<T> = any>(payload: Payload, options: Options): Promise<T> {
|
||||
export default async function restoreVersionLocal<T extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<GeneratedTypes['collections'][T]> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
@@ -33,7 +36,7 @@ export default async function restoreVersionLocal<T extends TypeWithVersion<T> =
|
||||
const collection = payload.collections[collectionSlug];
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
const i18n = i18nInit(payload.config.i18n);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Payload } from '../../..';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Payload } from '../../../payload';
|
||||
import { Document } from '../../../types';
|
||||
import getFileByPath from '../../../uploads/getFileByPath';
|
||||
import update from '../update';
|
||||
@@ -8,10 +9,10 @@ import { File } from '../../../uploads/types';
|
||||
import i18nInit from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options<T> = {
|
||||
collection: string
|
||||
export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
|
||||
collection: TSlug
|
||||
id: string | number
|
||||
data: Partial<T>
|
||||
data: Omit<GeneratedTypes['collections'][TSlug], 'id'>
|
||||
depth?: number
|
||||
locale?: string
|
||||
fallbackLocale?: string
|
||||
@@ -25,7 +26,10 @@ export type Options<T> = {
|
||||
autosave?: boolean
|
||||
}
|
||||
|
||||
export default async function updateLocal<T = any>(payload: Payload, options: Options<T>): Promise<T> {
|
||||
export default async function updateLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<TSlug>,
|
||||
): Promise<GeneratedTypes['collections'][TSlug]> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
@@ -46,7 +50,7 @@ export default async function updateLocal<T = any>(payload: Payload, options: Op
|
||||
const collection = payload.collections[collectionSlug];
|
||||
|
||||
if (!collection) {
|
||||
throw new APIError(`The collection with slug ${collectionSlug} can't be found.`);
|
||||
throw new APIError(`The collection with slug ${String(collectionSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
const i18n = i18nInit(payload.config.i18n);
|
||||
@@ -81,5 +85,5 @@ export default async function updateLocal<T = any>(payload: Payload, options: Op
|
||||
req,
|
||||
};
|
||||
|
||||
return update(args);
|
||||
return update<TSlug>(args);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import httpStatus from 'http-status';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Where, Document } from '../../types';
|
||||
import { Collection } from '../config/types';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
@@ -6,11 +7,8 @@ import executeAccess from '../../auth/executeAccess';
|
||||
import { NotFound, Forbidden, APIError, ValidationError } from '../../errors';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
import { saveCollectionDraft } from '../../versions/drafts/saveCollectionDraft';
|
||||
import { saveCollectionVersion } from '../../versions/saveCollectionVersion';
|
||||
import { saveVersion } from '../../versions/saveVersion';
|
||||
import { uploadFiles } from '../../uploads/uploadFiles';
|
||||
import cleanUpFailedVersion from '../../versions/cleanUpFailedVersion';
|
||||
import { ensurePublishedCollectionVersion } from '../../versions/ensurePublishedCollectionVersion';
|
||||
import { beforeChange } from '../../fields/hooks/beforeChange';
|
||||
import { beforeValidate } from '../../fields/hooks/beforeValidate';
|
||||
import { afterChange } from '../../fields/hooks/afterChange';
|
||||
@@ -18,11 +16,11 @@ import { afterRead } from '../../fields/hooks/afterRead';
|
||||
import { generateFileData } from '../../uploads/generateFileData';
|
||||
import { getLatestCollectionVersion } from '../../versions/getLatestCollectionVersion';
|
||||
|
||||
export type Arguments = {
|
||||
export type Arguments<T extends { [field: string | number | symbol]: unknown }> = {
|
||||
collection: Collection
|
||||
req: PayloadRequest
|
||||
id: string | number
|
||||
data: Record<string, unknown>
|
||||
data: Omit<T, 'id'>
|
||||
depth?: number
|
||||
disableVerificationEmail?: boolean
|
||||
overrideAccess?: boolean
|
||||
@@ -32,7 +30,9 @@ export type Arguments = {
|
||||
autosave?: boolean
|
||||
}
|
||||
|
||||
async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
incomingArgs: Arguments<GeneratedTypes['collections'][TSlug]>,
|
||||
): Promise<GeneratedTypes['collections'][TSlug]> {
|
||||
let args = incomingArgs;
|
||||
|
||||
// /////////////////////////////////////
|
||||
@@ -147,7 +147,7 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
// beforeValidate - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await beforeValidate({
|
||||
data = await beforeValidate<GeneratedTypes['collections'][TSlug]>({
|
||||
data,
|
||||
doc: originalDoc,
|
||||
entityConfig: collectionConfig,
|
||||
@@ -199,7 +199,7 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
// beforeChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
let result = await beforeChange({
|
||||
let result = await beforeChange<GeneratedTypes['collections'][TSlug]>({
|
||||
data,
|
||||
doc: originalDoc,
|
||||
docWithLocales,
|
||||
@@ -215,50 +215,17 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (shouldSavePassword) {
|
||||
await doc.setPassword(password as string);
|
||||
await doc.setPassword(password);
|
||||
await doc.save();
|
||||
delete data.password;
|
||||
delete result.password;
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Create version from existing doc
|
||||
// /////////////////////////////////////
|
||||
|
||||
let createdVersion;
|
||||
|
||||
if (collectionConfig.versions && !shouldSaveDraft) {
|
||||
createdVersion = await saveCollectionVersion({
|
||||
payload,
|
||||
config: collectionConfig,
|
||||
req,
|
||||
docWithLocales,
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Update
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (shouldSaveDraft) {
|
||||
await ensurePublishedCollectionVersion({
|
||||
payload,
|
||||
config: collectionConfig,
|
||||
req,
|
||||
docWithLocales,
|
||||
id,
|
||||
});
|
||||
|
||||
result = await saveCollectionDraft({
|
||||
payload,
|
||||
config: collectionConfig,
|
||||
req,
|
||||
data: result,
|
||||
id,
|
||||
autosave,
|
||||
});
|
||||
} else {
|
||||
if (!shouldSaveDraft) {
|
||||
try {
|
||||
result = await Model.findByIdAndUpdate(
|
||||
{ _id: id },
|
||||
@@ -266,27 +233,34 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
{ new: true },
|
||||
);
|
||||
} catch (error) {
|
||||
cleanUpFailedVersion({
|
||||
payload,
|
||||
entityConfig: collectionConfig,
|
||||
version: createdVersion,
|
||||
});
|
||||
|
||||
// Handle uniqueness error from MongoDB
|
||||
throw error.code === 11000 && error.keyValue
|
||||
? new ValidationError([{ message: 'Value must be unique', field: Object.keys(error.keyValue)[0] }], t)
|
||||
: error;
|
||||
}
|
||||
|
||||
const resultString = JSON.stringify(result);
|
||||
result = JSON.parse(resultString);
|
||||
|
||||
// custom id type reset
|
||||
result.id = result._id;
|
||||
}
|
||||
|
||||
result = JSON.parse(JSON.stringify(result));
|
||||
result.id = result._id as string | number;
|
||||
result = sanitizeInternalFields(result);
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Create version
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (collectionConfig.versions) {
|
||||
result = await saveVersion({
|
||||
payload,
|
||||
collection: collectionConfig,
|
||||
req,
|
||||
docWithLocales: result,
|
||||
id,
|
||||
autosave,
|
||||
draft: shouldSaveDraft,
|
||||
createdAt: originalDoc.createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
@@ -317,7 +291,7 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
// afterChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterChange({
|
||||
result = await afterChange<GeneratedTypes['collections'][TSlug]>({
|
||||
data,
|
||||
doc: result,
|
||||
previousDoc: originalDoc,
|
||||
|
||||
@@ -13,12 +13,16 @@ export type CreateResult = {
|
||||
|
||||
export default async function createHandler(req: PayloadRequest, res: Response, next: NextFunction): Promise<Response<CreateResult> | void> {
|
||||
try {
|
||||
const autosave = req.query.autosave === 'true';
|
||||
const draft = req.query.draft === 'true';
|
||||
|
||||
const doc = await create({
|
||||
req,
|
||||
collection: req.collection,
|
||||
data: req.body,
|
||||
depth: Number(req.query.depth),
|
||||
draft: req.query.draft === 'true',
|
||||
draft,
|
||||
autosave,
|
||||
});
|
||||
|
||||
return res.status(httpStatus.CREATED).json({
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable import/no-dynamic-require */
|
||||
/* eslint-disable global-require */
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import swcRegister from '@swc/register';
|
||||
import path from 'path';
|
||||
import pino from 'pino';
|
||||
import Logger from '../utilities/logger';
|
||||
@@ -15,24 +14,6 @@ const loadConfig = (logger?: pino.Logger): SanitizedConfig => {
|
||||
|
||||
const configPath = findConfig();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
swcRegister({
|
||||
sourceMaps: 'inline',
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
tsx: true,
|
||||
},
|
||||
},
|
||||
ignore: [
|
||||
/node_modules[\\/](?!.pnpm[\\/].*[\\/]node_modules[\\/])(?!payload[\\/]dist[\\/]admin|payload[\\/]components).*/,
|
||||
],
|
||||
module: {
|
||||
type: 'commonjs',
|
||||
},
|
||||
});
|
||||
|
||||
clientFiles.forEach((ext) => {
|
||||
require.extensions[ext] = () => null;
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ConnectOptions } from 'mongoose';
|
||||
import React from 'react';
|
||||
import { LoggerOptions } from 'pino';
|
||||
import type { InitOptions as i18nInitOptions } from 'i18next';
|
||||
import { Payload } from '..';
|
||||
import { Payload } from '../payload';
|
||||
import {
|
||||
AfterErrorHook,
|
||||
CollectionConfig,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Message } from './types';
|
||||
import { SendMailOptions } from 'nodemailer';
|
||||
import logger from '../utilities/logger';
|
||||
|
||||
export default async function sendEmail(message: Message): Promise<unknown> {
|
||||
export default async function sendEmail(message: SendMailOptions): Promise<unknown> {
|
||||
let result;
|
||||
try {
|
||||
const email = await this.email;
|
||||
|
||||
@@ -3,7 +3,7 @@ import compression from 'compression';
|
||||
import history from 'connect-history-api-fallback';
|
||||
import path from 'path';
|
||||
import initWebpack from '../webpack/init';
|
||||
import { Payload } from '../index';
|
||||
import { Payload } from '../payload';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import rateLimit from 'express-rate-limit';
|
||||
import localizationMiddleware from '../../localization/middleware';
|
||||
import authenticate from './authenticate';
|
||||
import identifyAPI from './identifyAPI';
|
||||
import { Payload } from '../..';
|
||||
import { Payload } from '../../payload';
|
||||
import { PayloadRequest } from '../types';
|
||||
import corsHeaders from './corsHeaders';
|
||||
import convertPayload from './convertPayload';
|
||||
|
||||
@@ -3,7 +3,7 @@ import passport from 'passport';
|
||||
import path from 'path';
|
||||
import getExecuteStaticAccess from '../auth/getExecuteStaticAccess';
|
||||
import authenticate from './middleware/authenticate';
|
||||
import { Payload } from '../index';
|
||||
import { Payload } from '../payload';
|
||||
import corsHeaders from './middleware/corsHeaders';
|
||||
|
||||
function initStatic(ctx: Payload): void {
|
||||
|
||||
@@ -2,11 +2,10 @@ import { Request } from 'express';
|
||||
import type { i18n as Ii18n, TFunction } from 'i18next';
|
||||
import DataLoader from 'dataloader';
|
||||
import { UploadedFile } from 'express-fileupload';
|
||||
import { Payload } from '../index';
|
||||
import { Collection } from '../collections/config/types';
|
||||
import { Payload } from '../payload';
|
||||
import { Collection, TypeWithID } from '../collections/config/types';
|
||||
import { User } from '../auth/types';
|
||||
import { Document } from '../types';
|
||||
import { TypeWithID } from '../globals/config/types';
|
||||
|
||||
/** Express request with some Payload related context added */
|
||||
export declare type PayloadRequest<U = any> = Request & {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { PayloadRequest } from '../../express/types';
|
||||
import { ConditionalDateProps } from '../../admin/components/elements/DatePicker/types';
|
||||
import { Description } from '../../admin/components/forms/FieldDescription/types';
|
||||
import { User } from '../../auth';
|
||||
import { Payload } from '../..';
|
||||
import { Payload } from '../../payload';
|
||||
import { RowLabel } from '../../admin/components/forms/RowLabel/types';
|
||||
|
||||
export type FieldHookArgs<T extends TypeWithID = any, P = any, S = any> = {
|
||||
|
||||
@@ -4,23 +4,23 @@ import { PayloadRequest } from '../../../express/types';
|
||||
import { traverseFields } from './traverseFields';
|
||||
import deepCopyObject from '../../../utilities/deepCopyObject';
|
||||
|
||||
type Args = {
|
||||
data: Record<string, unknown>
|
||||
doc: Record<string, unknown>
|
||||
previousDoc: Record<string, unknown>
|
||||
type Args<T> = {
|
||||
data: Omit<T, 'id'>
|
||||
doc: T
|
||||
previousDoc: T
|
||||
entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig
|
||||
operation: 'create' | 'update'
|
||||
req: PayloadRequest
|
||||
}
|
||||
|
||||
export const afterChange = async ({
|
||||
export const afterChange = async <T extends Record<string, unknown>>({
|
||||
data,
|
||||
doc: incomingDoc,
|
||||
previousDoc,
|
||||
entityConfig,
|
||||
operation,
|
||||
req,
|
||||
}: Args): Promise<Record<string, unknown>> => {
|
||||
}: Args<T>): Promise<T> => {
|
||||
const doc = deepCopyObject(incomingDoc);
|
||||
|
||||
await traverseFields({
|
||||
|
||||
@@ -6,9 +6,9 @@ import { traverseFields } from './traverseFields';
|
||||
import { ValidationError } from '../../../errors';
|
||||
import deepCopyObject from '../../../utilities/deepCopyObject';
|
||||
|
||||
type Args = {
|
||||
data: Record<string, unknown>
|
||||
doc: Record<string, unknown>
|
||||
type Args<T> = {
|
||||
data: Omit<T, 'id'>
|
||||
doc: T
|
||||
docWithLocales: Record<string, unknown>
|
||||
entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig
|
||||
id?: string | number
|
||||
@@ -17,7 +17,7 @@ type Args = {
|
||||
skipValidation?: boolean
|
||||
}
|
||||
|
||||
export const beforeChange = async ({
|
||||
export const beforeChange = async <T extends Record<string, unknown>>({
|
||||
data: incomingData,
|
||||
doc,
|
||||
docWithLocales,
|
||||
@@ -26,7 +26,7 @@ export const beforeChange = async ({
|
||||
operation,
|
||||
req,
|
||||
skipValidation,
|
||||
}: Args): Promise<Record<string, unknown>> => {
|
||||
}: Args<T>): Promise<T> => {
|
||||
const data = deepCopyObject(incomingData);
|
||||
const mergeLocaleActions = [];
|
||||
const errors: { message: string, field: string }[] = [];
|
||||
|
||||
@@ -4,9 +4,9 @@ import { PayloadRequest } from '../../../express/types';
|
||||
import { traverseFields } from './traverseFields';
|
||||
import deepCopyObject from '../../../utilities/deepCopyObject';
|
||||
|
||||
type Args = {
|
||||
data: Record<string, unknown>
|
||||
doc: Record<string, unknown>
|
||||
type Args<T> = {
|
||||
data: Omit<T, 'id'>
|
||||
doc?: T | Record<string, unknown>
|
||||
entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig
|
||||
id?: string | number
|
||||
operation: 'create' | 'update'
|
||||
@@ -14,7 +14,7 @@ type Args = {
|
||||
req: PayloadRequest
|
||||
}
|
||||
|
||||
export const beforeValidate = async ({
|
||||
export const beforeValidate = async <T extends Record<string, unknown>>({
|
||||
data: incomingData,
|
||||
doc,
|
||||
entityConfig,
|
||||
@@ -22,7 +22,7 @@ export const beforeValidate = async ({
|
||||
operation,
|
||||
overrideAccess,
|
||||
req,
|
||||
}: Args): Promise<Record<string, unknown>> => {
|
||||
}: Args<T>): Promise<T> => {
|
||||
const data = deepCopyObject(incomingData);
|
||||
|
||||
await traverseFields({
|
||||
|
||||
@@ -3,9 +3,9 @@ import { PayloadRequest } from '../../../express/types';
|
||||
import { Field, fieldAffectsData, TabAsField, tabHasName, valueIsValueWithRelation } from '../../config/types';
|
||||
import { traverseFields } from './traverseFields';
|
||||
|
||||
type Args = {
|
||||
data: Record<string, unknown>
|
||||
doc: Record<string, unknown>
|
||||
type Args<T> = {
|
||||
data: T
|
||||
doc: T
|
||||
field: Field | TabAsField
|
||||
id?: string | number
|
||||
operation: 'create' | 'update'
|
||||
@@ -20,7 +20,7 @@ type Args = {
|
||||
// - Execute field hooks
|
||||
// - Execute field access control
|
||||
|
||||
export const promise = async ({
|
||||
export const promise = async <T>({
|
||||
data,
|
||||
doc,
|
||||
field,
|
||||
@@ -30,7 +30,7 @@ export const promise = async ({
|
||||
req,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
}: Args): Promise<void> => {
|
||||
}: Args<T>): Promise<void> => {
|
||||
if (fieldAffectsData(field)) {
|
||||
if (field.name === 'id') {
|
||||
if (field.type === 'number' && typeof siblingData[field.name] === 'string') {
|
||||
|
||||
@@ -2,9 +2,9 @@ import { PayloadRequest } from '../../../express/types';
|
||||
import { Field, TabAsField } from '../../config/types';
|
||||
import { promise } from './promise';
|
||||
|
||||
type Args = {
|
||||
data: Record<string, unknown>
|
||||
doc: Record<string, unknown>
|
||||
type Args<T> = {
|
||||
data: T
|
||||
doc: T
|
||||
fields: (Field | TabAsField)[]
|
||||
id?: string | number
|
||||
operation: 'create' | 'update'
|
||||
@@ -14,7 +14,7 @@ type Args = {
|
||||
siblingDoc: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const traverseFields = async ({
|
||||
export const traverseFields = async <T>({
|
||||
data,
|
||||
doc,
|
||||
fields,
|
||||
@@ -24,7 +24,7 @@ export const traverseFields = async ({
|
||||
req,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
}: Args): Promise<void> => {
|
||||
}: Args<T>): Promise<void> => {
|
||||
const promises = [];
|
||||
fields.forEach((field) => {
|
||||
promises.push(promise({
|
||||
|
||||
@@ -212,7 +212,7 @@ const validateFilterOptions: Validate = async (value, { t, filterOptions, id, us
|
||||
}
|
||||
});
|
||||
|
||||
const result = await payload.find<TypeWithID>({
|
||||
const result = await payload.find({
|
||||
collection,
|
||||
depth: 0,
|
||||
where: {
|
||||
|
||||
15
src/generated-types.ts
Normal file
15
src/generated-types.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// This is a stub for Payload's generated types.
|
||||
// It will not be used.
|
||||
// Instead, configure a path within your `tsconfig.json`'s `compilerOptions.paths` to point to your generated types.
|
||||
|
||||
import { TypeWithID } from './collections/config/types';
|
||||
import { TypeWithID as GlobalTypeWithID } from './globals/config/types';
|
||||
|
||||
export type Config = {
|
||||
collections: {
|
||||
[slug: string | number | symbol]: TypeWithID
|
||||
}
|
||||
globals: {
|
||||
[slug: string | number | symbol]: GlobalTypeWithID
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import updateResolver from './resolvers/update';
|
||||
import findVersionByIDResolver from './resolvers/findVersionByID';
|
||||
import findVersionsResolver from './resolvers/findVersions';
|
||||
import restoreVersionResolver from './resolvers/restoreVersion';
|
||||
import { Payload } from '../..';
|
||||
import { Payload } from '../../payload';
|
||||
import buildObjectType from '../../graphql/schema/buildObjectType';
|
||||
import buildMutationInputType from '../../graphql/schema/buildMutationInputType';
|
||||
import buildWhereInputType from '../../graphql/schema/buildWhereInputType';
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { SanitizedGlobalConfig } from '../../config/types';
|
||||
import update from '../../operations/update';
|
||||
|
||||
type Resolver = (
|
||||
type Resolver<TSlug extends keyof GeneratedTypes['globals']> = (
|
||||
_: unknown,
|
||||
args: {
|
||||
locale?: string
|
||||
fallbackLocale?: string
|
||||
data?: Record<string, unknown>
|
||||
data?: GeneratedTypes['globals'][TSlug]
|
||||
draft?: boolean
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequest,
|
||||
res: Response
|
||||
}
|
||||
) => Promise<Document>
|
||||
) => Promise<GeneratedTypes['globals'][TSlug]>
|
||||
|
||||
export default function updateResolver(globalConfig: SanitizedGlobalConfig): Resolver {
|
||||
export default function updateResolver<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
globalConfig: SanitizedGlobalConfig,
|
||||
): Resolver<TSlug> {
|
||||
return async function resolver(_, args, context) {
|
||||
if (args.locale) context.req.locale = args.locale;
|
||||
if (args.fallbackLocale) context.req.fallbackLocale = args.fallbackLocale;
|
||||
@@ -34,7 +36,7 @@ export default function updateResolver(globalConfig: SanitizedGlobalConfig): Res
|
||||
draft: args.draft,
|
||||
};
|
||||
|
||||
const result = await update(options);
|
||||
const result = await update<TSlug>(options);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
19
src/globals/initHTTP.ts
Normal file
19
src/globals/initHTTP.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import express from 'express';
|
||||
import mountEndpoints from '../express/mountEndpoints';
|
||||
import buildEndpoints from './buildEndpoints';
|
||||
import { SanitizedGlobalConfig } from './config/types';
|
||||
import { Payload } from '../payload';
|
||||
|
||||
export default function initGlobals(ctx: Payload): void {
|
||||
if (ctx.config.globals) {
|
||||
ctx.config.globals.forEach((global: SanitizedGlobalConfig) => {
|
||||
const router = express.Router();
|
||||
const { slug } = global;
|
||||
|
||||
const endpoints = buildEndpoints(global);
|
||||
mountEndpoints(ctx.express, router, endpoints);
|
||||
|
||||
ctx.router.use(`/globals/${slug}`, router);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,14 @@
|
||||
import express from 'express';
|
||||
import mongoose from 'mongoose';
|
||||
import paginate from 'mongoose-paginate-v2';
|
||||
import buildQueryPlugin from '../mongoose/buildQuery';
|
||||
import buildModel from './buildModel';
|
||||
import { Payload } from '../index';
|
||||
import { Payload } from '../payload';
|
||||
import { getVersionsModelName } from '../versions/getVersionsModelName';
|
||||
import { buildVersionGlobalFields } from '../versions/buildGlobalFields';
|
||||
import buildSchema from '../mongoose/buildSchema';
|
||||
import { CollectionModel } from '../collections/config/types';
|
||||
import mountEndpoints from '../express/mountEndpoints';
|
||||
import buildEndpoints from './buildEndpoints';
|
||||
import { SanitizedGlobalConfig } from './config/types';
|
||||
|
||||
export default function initGlobals(ctx: Payload): void {
|
||||
export default function initGlobalsLocal(ctx: Payload): void {
|
||||
if (ctx.config.globals) {
|
||||
ctx.globals = {
|
||||
Model: buildModel(ctx.config),
|
||||
@@ -30,7 +26,7 @@ export default function initGlobals(ctx: Payload): void {
|
||||
disableUnique: true,
|
||||
draftsEnabled: true,
|
||||
options: {
|
||||
timestamps: true,
|
||||
timestamps: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -41,18 +37,5 @@ export default function initGlobals(ctx: Payload): void {
|
||||
ctx.versions[global.slug] = mongoose.model(versionModelName, versionSchema) as CollectionModel;
|
||||
}
|
||||
});
|
||||
|
||||
// If not local, open routes
|
||||
if (!ctx.local) {
|
||||
ctx.config.globals.forEach((global: SanitizedGlobalConfig) => {
|
||||
const router = express.Router();
|
||||
const { slug } = global;
|
||||
|
||||
const endpoints = buildEndpoints(global);
|
||||
mountEndpoints(ctx.express, router, endpoints);
|
||||
|
||||
ctx.router.use(`/globals/${slug}`, router);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { AccessResult } from '../../config/types';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import replaceWithDraftIfAvailable from '../../versions/drafts/replaceWithDraftIfAvailable';
|
||||
import { afterRead } from '../../fields/hooks/afterRead';
|
||||
import { SanitizedGlobalConfig, TypeWithID } from '../config/types';
|
||||
import { SanitizedGlobalConfig } from '../config/types';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
|
||||
type Args = {
|
||||
@@ -19,7 +19,7 @@ type Args = {
|
||||
overrideAccess?: boolean
|
||||
}
|
||||
|
||||
async function findOne<T extends TypeWithID = any>(args: Args): Promise<T> {
|
||||
async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T> {
|
||||
const {
|
||||
globalConfig,
|
||||
locale,
|
||||
|
||||
@@ -6,10 +6,10 @@ import { PaginatedDocs } from '../../mongoose/types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
import flattenWhereConstraints from '../../utilities/flattenWhereConstraints';
|
||||
import { buildSortParam } from '../../mongoose/buildSortParam';
|
||||
import { TypeWithVersion } from '../../versions/types';
|
||||
import { SanitizedGlobalConfig } from '../config/types';
|
||||
import { afterRead } from '../../fields/hooks/afterRead';
|
||||
import { buildVersionGlobalFields } from '../../versions/buildGlobalFields';
|
||||
import { TypeWithVersion } from '../../versions/types';
|
||||
|
||||
export type Arguments = {
|
||||
globalConfig: SanitizedGlobalConfig
|
||||
@@ -23,7 +23,9 @@ export type Arguments = {
|
||||
showHiddenFields?: boolean
|
||||
}
|
||||
|
||||
async function findVersions<T extends TypeWithVersion<T> = any>(args: Arguments): Promise<PaginatedDocs<T>> {
|
||||
async function findVersions<T extends TypeWithVersion<T>>(
|
||||
args: Arguments,
|
||||
): Promise<PaginatedDocs<T>> {
|
||||
const {
|
||||
where,
|
||||
page,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Payload } from '../../..';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Payload } from '../../../payload';
|
||||
import { getDataLoader } from '../../../collections/dataloader';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { Document } from '../../../types';
|
||||
import { TypeWithID } from '../../config/types';
|
||||
import findOne from '../findOne';
|
||||
import i18nInit from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
slug: string
|
||||
export type Options<T extends keyof GeneratedTypes['globals']> = {
|
||||
slug: T
|
||||
depth?: number
|
||||
locale?: string
|
||||
fallbackLocale?: string
|
||||
@@ -18,7 +18,10 @@ export type Options = {
|
||||
draft?: boolean
|
||||
}
|
||||
|
||||
export default async function findOneLocal<T extends TypeWithID = any>(payload: Payload, options: Options): Promise<T> {
|
||||
export default async function findOneLocal<T extends keyof GeneratedTypes['globals']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<GeneratedTypes['globals'][T]> {
|
||||
const {
|
||||
slug: globalSlug,
|
||||
depth,
|
||||
@@ -35,7 +38,7 @@ export default async function findOneLocal<T extends TypeWithID = any>(payload:
|
||||
|
||||
|
||||
if (!globalConfig) {
|
||||
throw new APIError(`The global with slug ${globalSlug} can't be found.`);
|
||||
throw new APIError(`The global with slug ${String(globalSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
const req = {
|
||||
@@ -51,7 +54,7 @@ export default async function findOneLocal<T extends TypeWithID = any>(payload:
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);
|
||||
|
||||
return findOne({
|
||||
slug: globalSlug,
|
||||
slug: globalSlug as string,
|
||||
depth,
|
||||
globalConfig,
|
||||
overrideAccess,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Payload } from '../../..';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Payload } from '../../../payload';
|
||||
import { getDataLoader } from '../../../collections/dataloader';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { Document } from '../../../types';
|
||||
@@ -7,8 +8,8 @@ import findVersionByID from '../findVersionByID';
|
||||
import i18nInit from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
slug: string
|
||||
export type Options<T extends keyof GeneratedTypes['globals']> = {
|
||||
slug: T
|
||||
id: string
|
||||
depth?: number
|
||||
locale?: string
|
||||
@@ -19,7 +20,10 @@ export type Options = {
|
||||
disableErrors?: boolean
|
||||
}
|
||||
|
||||
export default async function findVersionByIDLocal<T extends TypeWithVersion<T> = any>(payload: Payload, options: Options): Promise<T> {
|
||||
export default async function findVersionByIDLocal<T extends keyof GeneratedTypes['globals']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<TypeWithVersion<GeneratedTypes['globals'][T]>> {
|
||||
const {
|
||||
slug: globalSlug,
|
||||
depth,
|
||||
@@ -36,7 +40,7 @@ export default async function findVersionByIDLocal<T extends TypeWithVersion<T>
|
||||
const i18n = i18nInit(payload.config.i18n);
|
||||
|
||||
if (!globalConfig) {
|
||||
throw new APIError(`The global with slug ${globalSlug} can't be found.`);
|
||||
throw new APIError(`The global with slug ${String(globalSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
const req = {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Document, Where } from '../../../types';
|
||||
import { PaginatedDocs } from '../../../mongoose/types';
|
||||
import { TypeWithVersion } from '../../../versions/types';
|
||||
import { Payload } from '../../..';
|
||||
import { Payload } from '../../../payload';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import findVersions from '../findVersions';
|
||||
import { getDataLoader } from '../../../collections/dataloader';
|
||||
import i18nInit from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
import { TypeWithVersion } from '../../../versions/types';
|
||||
|
||||
export type Options = {
|
||||
slug: string
|
||||
export type Options<T extends keyof GeneratedTypes['globals']> = {
|
||||
slug: T
|
||||
depth?: number
|
||||
page?: number
|
||||
limit?: number
|
||||
@@ -22,7 +23,10 @@ export type Options = {
|
||||
where?: Where
|
||||
}
|
||||
|
||||
export default async function findVersionsLocal<T extends TypeWithVersion<T> = any>(payload: Payload, options: Options): Promise<PaginatedDocs<T>> {
|
||||
export default async function findVersionsLocal<T extends keyof GeneratedTypes['globals']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<PaginatedDocs<TypeWithVersion<GeneratedTypes['globals'][T]>>> {
|
||||
const {
|
||||
slug: globalSlug,
|
||||
depth,
|
||||
@@ -41,7 +45,7 @@ export default async function findVersionsLocal<T extends TypeWithVersion<T> = a
|
||||
const i18n = i18nInit(payload.config.i18n);
|
||||
|
||||
if (!globalConfig) {
|
||||
throw new APIError(`The global with slug ${globalSlug} can't be found.`);
|
||||
throw new APIError(`The global with slug ${String(globalSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
const req = {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Payload } from '../../..';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Payload } from '../../../payload';
|
||||
import { getDataLoader } from '../../../collections/dataloader';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { Document } from '../../../types';
|
||||
import { TypeWithVersion } from '../../../versions/types';
|
||||
import restoreVersion from '../restoreVersion';
|
||||
import i18nInit from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
export type Options<T extends keyof GeneratedTypes['globals']> = {
|
||||
slug: string
|
||||
id: string
|
||||
depth?: number
|
||||
@@ -18,7 +18,10 @@ export type Options = {
|
||||
showHiddenFields?: boolean
|
||||
}
|
||||
|
||||
export default async function restoreVersionLocal<T extends TypeWithVersion<T> = any>(payload: Payload, options: Options): Promise<T> {
|
||||
export default async function restoreVersionLocal<T extends keyof GeneratedTypes['globals']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<GeneratedTypes['globals'][T]> {
|
||||
const {
|
||||
slug: globalSlug,
|
||||
depth,
|
||||
@@ -34,7 +37,7 @@ export default async function restoreVersionLocal<T extends TypeWithVersion<T> =
|
||||
const i18n = i18nInit(payload.config.i18n);
|
||||
|
||||
if (!globalConfig) {
|
||||
throw new APIError(`The global with slug ${globalSlug} can't be found.`);
|
||||
throw new APIError(`The global with slug ${String(globalSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
const req = {
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import { Payload } from '../../..';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { Payload } from '../../../payload';
|
||||
import { Document } from '../../../types';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { TypeWithID } from '../../config/types';
|
||||
import update from '../update';
|
||||
import { getDataLoader } from '../../../collections/dataloader';
|
||||
import i18nInit from '../../../translations/init';
|
||||
import { APIError } from '../../../errors';
|
||||
|
||||
export type Options = {
|
||||
slug: string
|
||||
export type Options<TSlug extends keyof GeneratedTypes['globals']> = {
|
||||
slug: TSlug
|
||||
depth?: number
|
||||
locale?: string
|
||||
fallbackLocale?: string
|
||||
data: Record<string, unknown>
|
||||
data: Omit<GeneratedTypes['globals'][TSlug], 'id'>
|
||||
user?: Document
|
||||
overrideAccess?: boolean
|
||||
showHiddenFields?: boolean
|
||||
draft?: boolean
|
||||
}
|
||||
|
||||
export default async function updateLocal<T extends TypeWithID = any>(payload: Payload, options: Options): Promise<T> {
|
||||
export default async function updateLocal<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
payload: Payload,
|
||||
options: Options<TSlug>,
|
||||
): Promise<GeneratedTypes['globals'][TSlug]> {
|
||||
const {
|
||||
slug: globalSlug,
|
||||
depth,
|
||||
@@ -36,7 +39,7 @@ export default async function updateLocal<T extends TypeWithID = any>(payload: P
|
||||
const i18n = i18nInit(payload.config.i18n);
|
||||
|
||||
if (!globalConfig) {
|
||||
throw new APIError(`The global with slug ${globalSlug} can't be found.`);
|
||||
throw new APIError(`The global with slug ${String(globalSlug)} can't be found.`);
|
||||
}
|
||||
|
||||
const req = {
|
||||
@@ -51,7 +54,7 @@ export default async function updateLocal<T extends TypeWithID = any>(payload: P
|
||||
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);
|
||||
|
||||
return update({
|
||||
return update<TSlug>({
|
||||
slug: globalSlug,
|
||||
data,
|
||||
depth,
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { docHasTimestamps, Where } from '../../types';
|
||||
import { SanitizedGlobalConfig, TypeWithID } from '../config/types';
|
||||
import { SanitizedGlobalConfig } from '../config/types';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { saveGlobalVersion } from '../../versions/saveGlobalVersion';
|
||||
import { saveGlobalDraft } from '../../versions/drafts/saveGlobalDraft';
|
||||
import { ensurePublishedGlobalVersion } from '../../versions/ensurePublishedGlobalVersion';
|
||||
import cleanUpFailedVersion from '../../versions/cleanUpFailedVersion';
|
||||
import { hasWhereAccessResult } from '../../auth';
|
||||
import { beforeChange } from '../../fields/hooks/beforeChange';
|
||||
import { beforeValidate } from '../../fields/hooks/beforeValidate';
|
||||
import { afterChange } from '../../fields/hooks/afterChange';
|
||||
import { afterRead } from '../../fields/hooks/afterRead';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import { saveVersion } from '../../versions/saveVersion';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
|
||||
type Args = {
|
||||
type Args<T extends { [field: string | number | symbol]: unknown }> = {
|
||||
globalConfig: SanitizedGlobalConfig
|
||||
slug: string
|
||||
slug: string | number | symbol
|
||||
req: PayloadRequest
|
||||
depth?: number
|
||||
overrideAccess?: boolean
|
||||
showHiddenFields?: boolean
|
||||
draft?: boolean
|
||||
autosave?: boolean
|
||||
data: Record<string, unknown>
|
||||
data: Omit<T, 'id'>
|
||||
}
|
||||
|
||||
async function update<T extends TypeWithID = any>(args: Args): Promise<T> {
|
||||
async function update<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
args: Args<GeneratedTypes['globals'][TSlug]>,
|
||||
): Promise<GeneratedTypes['globals'][TSlug]> {
|
||||
const {
|
||||
globalConfig,
|
||||
slug,
|
||||
@@ -182,57 +182,20 @@ async function update<T extends TypeWithID = any>(args: Args): Promise<T> {
|
||||
skipValidation: shouldSaveDraft,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Create version from existing doc
|
||||
// /////////////////////////////////////
|
||||
|
||||
let createdVersion;
|
||||
|
||||
if (globalConfig.versions && !shouldSaveDraft) {
|
||||
createdVersion = await saveGlobalVersion({
|
||||
payload,
|
||||
config: globalConfig,
|
||||
req,
|
||||
docWithLocales: result,
|
||||
});
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Update
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (shouldSaveDraft) {
|
||||
await ensurePublishedGlobalVersion({
|
||||
payload,
|
||||
config: globalConfig,
|
||||
req,
|
||||
docWithLocales: result,
|
||||
});
|
||||
|
||||
global = await saveGlobalDraft({
|
||||
payload,
|
||||
config: globalConfig,
|
||||
data: result,
|
||||
autosave,
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
if (existingGlobal) {
|
||||
global = await Model.findOneAndUpdate(
|
||||
{ globalType: slug },
|
||||
result,
|
||||
{ new: true },
|
||||
);
|
||||
} else {
|
||||
result.globalType = slug;
|
||||
global = await Model.create(result);
|
||||
}
|
||||
} catch (error) {
|
||||
cleanUpFailedVersion({
|
||||
payload,
|
||||
entityConfig: globalConfig,
|
||||
version: createdVersion,
|
||||
});
|
||||
if (!shouldSaveDraft) {
|
||||
if (existingGlobal) {
|
||||
global = await Model.findOneAndUpdate(
|
||||
{ globalType: slug },
|
||||
result,
|
||||
{ new: true },
|
||||
);
|
||||
} else {
|
||||
result.globalType = slug;
|
||||
global = await Model.create(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +203,22 @@ async function update<T extends TypeWithID = any>(args: Args): Promise<T> {
|
||||
global = JSON.parse(global);
|
||||
global = sanitizeInternalFields(global);
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Create version
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (globalConfig.versions) {
|
||||
global = await saveVersion({
|
||||
payload,
|
||||
global: globalConfig,
|
||||
req,
|
||||
docWithLocales: result,
|
||||
autosave,
|
||||
draft: shouldSaveDraft,
|
||||
createdAt: global.createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import graphQLPlayground from 'graphql-playground-middleware-express';
|
||||
import { Payload } from '../index';
|
||||
import { Payload } from '../payload';
|
||||
|
||||
function initPlayground(ctx: Payload): void {
|
||||
if ((!ctx.config.graphQL.disable && !ctx.config.graphQL.disablePlaygroundInProduction && process.env.NODE_ENV === 'production') || process.env.NODE_ENV !== 'production') {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import * as GraphQL from 'graphql';
|
||||
import { GraphQLObjectType, GraphQLSchema } from 'graphql';
|
||||
import queryComplexity, { fieldExtensionsEstimator, simpleEstimator } from 'graphql-query-complexity';
|
||||
import { Payload } from '..';
|
||||
import { Payload } from '../payload';
|
||||
import buildLocaleInputType from './schema/buildLocaleInputType';
|
||||
import buildFallbackLocaleInputType from './schema/buildFallbackLocaleInputType';
|
||||
import initCollections from '../collections/graphql/init';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { Payload } from '../..';
|
||||
import { Payload } from '../../payload';
|
||||
import { Block } from '../../fields/config/types';
|
||||
import buildObjectType from './buildObjectType';
|
||||
import { toWords } from '../../utilities/formatLabels';
|
||||
|
||||
@@ -18,7 +18,7 @@ import formatName from '../utilities/formatName';
|
||||
import combineParentName from '../utilities/combineParentName';
|
||||
import { ArrayField, CodeField, JSONField, DateField, EmailField, Field, fieldAffectsData, GroupField, NumberField, PointField, RadioField, RelationshipField, RichTextField, RowField, SelectField, TextareaField, TextField, UploadField, CollapsibleField, TabsField, CheckboxField, BlockField, tabHasName } from '../../fields/config/types';
|
||||
import { toWords } from '../../utilities/formatLabels';
|
||||
import { Payload } from '../../index';
|
||||
import { Payload } from '../../payload';
|
||||
import { SanitizedCollectionConfig } from '../../collections/config/types';
|
||||
import { groupOrTabHasRequiredSubfield } from '../../utilities/groupOrTabHasRequiredSubfield';
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ import withNullableType from './withNullableType';
|
||||
import { toWords } from '../../utilities/formatLabels';
|
||||
import createRichTextRelationshipPromise from '../../fields/richText/richTextRelationshipPromise';
|
||||
import formatOptions from '../utilities/formatOptions';
|
||||
import { Payload } from '../..';
|
||||
import { Payload } from '../../payload';
|
||||
import buildWhereInputType from './buildWhereInputType';
|
||||
import buildBlockType from './buildBlockType';
|
||||
import isFieldNullable from './isFieldNullable';
|
||||
|
||||
@@ -5,7 +5,7 @@ import formatName from '../utilities/formatName';
|
||||
import { CollectionConfig, SanitizedCollectionConfig } from '../../collections/config/types';
|
||||
import { GlobalConfig, SanitizedGlobalConfig } from '../../globals/config/types';
|
||||
import { Field } from '../../fields/config/types';
|
||||
import { Payload } from '../..';
|
||||
import { Payload } from '../../payload';
|
||||
import { toWords } from '../../utilities/formatLabels';
|
||||
|
||||
type OperationType = 'create' | 'read' | 'update' | 'delete' | 'unlock' | 'readVersions';
|
||||
|
||||
277
src/index.ts
277
src/index.ts
@@ -1,280 +1,23 @@
|
||||
import { Express, Router } from 'express';
|
||||
import pino from 'pino';
|
||||
import { GraphQLError, GraphQLFormattedError, GraphQLSchema } from 'graphql';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import {
|
||||
TypeWithID,
|
||||
Collection,
|
||||
CollectionModel,
|
||||
} from './collections/config/types';
|
||||
import {
|
||||
SanitizedConfig,
|
||||
EmailOptions,
|
||||
InitOptions,
|
||||
} from './config/types';
|
||||
import { TypeWithVersion } from './versions/types';
|
||||
import { PaginatedDocs } from './mongoose/types';
|
||||
import { initHTTP } from './initHTTP';
|
||||
import { Payload, BasePayload } from './payload';
|
||||
|
||||
import { PayloadAuthenticate } from './express/middleware/authenticate';
|
||||
import { Globals, TypeWithID as GlobalTypeWithID } from './globals/config/types';
|
||||
import { ErrorHandler } from './express/middleware/errorHandler';
|
||||
import localOperations from './collections/operations/local';
|
||||
import localGlobalOperations from './globals/operations/local';
|
||||
import { encrypt, decrypt } from './auth/crypto';
|
||||
import { BuildEmailResult, Message } from './email/types';
|
||||
import { Preferences } from './preferences/types';
|
||||
|
||||
import { Options as CreateOptions } from './collections/operations/local/create';
|
||||
import { Options as FindOptions } from './collections/operations/local/find';
|
||||
import { Options as FindByIDOptions } from './collections/operations/local/findByID';
|
||||
import { Options as UpdateOptions } from './collections/operations/local/update';
|
||||
import { Options as DeleteOptions } from './collections/operations/local/delete';
|
||||
import { Options as FindVersionsOptions } from './collections/operations/local/findVersions';
|
||||
import { Options as FindVersionByIDOptions } from './collections/operations/local/findVersionByID';
|
||||
import { Options as RestoreVersionOptions } from './collections/operations/local/restoreVersion';
|
||||
import { Options as FindGlobalVersionsOptions } from './globals/operations/local/findVersions';
|
||||
import { Options as FindGlobalVersionByIDOptions } from './globals/operations/local/findVersionByID';
|
||||
import { Options as RestoreGlobalVersionOptions } from './globals/operations/local/restoreVersion';
|
||||
import { Options as ForgotPasswordOptions } from './auth/operations/local/forgotPassword';
|
||||
import { Options as LoginOptions } from './auth/operations/local/login';
|
||||
import { Options as ResetPasswordOptions } from './auth/operations/local/resetPassword';
|
||||
import { Options as UnlockOptions } from './auth/operations/local/unlock';
|
||||
import { Options as VerifyEmailOptions } from './auth/operations/local/verifyEmail';
|
||||
import { Result as ForgotPasswordResult } from './auth/operations/forgotPassword';
|
||||
import { Result as ResetPasswordResult } from './auth/operations/resetPassword';
|
||||
import { Result as LoginResult } from './auth/operations/login';
|
||||
import { Options as FindGlobalOptions } from './globals/operations/local/findOne';
|
||||
import { Options as UpdateGlobalOptions } from './globals/operations/local/update';
|
||||
import { initSync, initAsync } from './init';
|
||||
export { getPayload } from './payload';
|
||||
|
||||
require('isomorphic-fetch');
|
||||
|
||||
/**
|
||||
* @description Payload
|
||||
*/
|
||||
export class Payload {
|
||||
config: SanitizedConfig;
|
||||
|
||||
collections: {
|
||||
[slug: string]: Collection;
|
||||
} = {}
|
||||
|
||||
versions: {
|
||||
[slug: string]: CollectionModel;
|
||||
} = {}
|
||||
|
||||
preferences: Preferences;
|
||||
|
||||
globals: Globals;
|
||||
|
||||
logger: pino.Logger;
|
||||
|
||||
express: Express
|
||||
|
||||
router: Router;
|
||||
|
||||
emailOptions: EmailOptions;
|
||||
|
||||
email: BuildEmailResult;
|
||||
|
||||
sendEmail: (message: Message) => Promise<unknown>;
|
||||
|
||||
secret: string;
|
||||
|
||||
mongoURL: string | false;
|
||||
|
||||
mongoMemoryServer: any
|
||||
|
||||
local: boolean;
|
||||
|
||||
encrypt = encrypt;
|
||||
|
||||
decrypt = decrypt;
|
||||
|
||||
errorHandler: ErrorHandler;
|
||||
|
||||
authenticate: PayloadAuthenticate;
|
||||
|
||||
types: {
|
||||
blockTypes: any;
|
||||
blockInputTypes: any;
|
||||
localeInputType?: any;
|
||||
fallbackLocaleInputType?: any;
|
||||
};
|
||||
|
||||
Query: { name: string; fields: { [key: string]: any } } = { name: 'Query', fields: {} };
|
||||
|
||||
Mutation: { name: string; fields: { [key: string]: any } } = { name: 'Mutation', fields: {} };
|
||||
|
||||
schema: GraphQLSchema;
|
||||
|
||||
extensions: (info: any) => Promise<any>;
|
||||
|
||||
customFormatErrorFn: (error: GraphQLError) => GraphQLFormattedError;
|
||||
|
||||
validationRules: any;
|
||||
|
||||
errorResponses: GraphQLFormattedError[] = [];
|
||||
|
||||
errorIndex: number;
|
||||
|
||||
/**
|
||||
* @description Initializes Payload
|
||||
* @param options
|
||||
*/
|
||||
init(options: InitOptions): void {
|
||||
initSync(this, options);
|
||||
}
|
||||
|
||||
async initAsync(options: InitOptions): Promise<void> {
|
||||
await initAsync(this, options);
|
||||
}
|
||||
|
||||
getAdminURL = (): string => `${this.config.serverURL}${this.config.routes.admin}`;
|
||||
|
||||
getAPIURL = (): string => `${this.config.serverURL}${this.config.routes.api}`;
|
||||
|
||||
/**
|
||||
* @description Performs create operation
|
||||
* @param options
|
||||
* @returns created document
|
||||
*/
|
||||
create = async <T = any>(options: CreateOptions<T>): Promise<T> => {
|
||||
const { create } = localOperations;
|
||||
return create(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find documents with criteria
|
||||
* @param options
|
||||
* @returns documents satisfying query
|
||||
*/
|
||||
find = async <T extends TypeWithID = any>(options: FindOptions): Promise<PaginatedDocs<T>> => {
|
||||
const { find } = localOperations;
|
||||
return find(this, options);
|
||||
}
|
||||
|
||||
findGlobal = async <T extends GlobalTypeWithID = any>(options: FindGlobalOptions): Promise<T> => {
|
||||
const { findOne } = localGlobalOperations;
|
||||
return findOne(this, options);
|
||||
}
|
||||
|
||||
updateGlobal = async <T extends GlobalTypeWithID = any>(options: UpdateGlobalOptions): Promise<T> => {
|
||||
const { update } = localGlobalOperations;
|
||||
return update(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find global versions with criteria
|
||||
* @param options
|
||||
* @returns versions satisfying query
|
||||
*/
|
||||
findGlobalVersions = async <T extends TypeWithVersion<T> = any>(options: FindGlobalVersionsOptions): Promise<PaginatedDocs<T>> => {
|
||||
const { findVersions } = localGlobalOperations;
|
||||
return findVersions<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find global version by ID
|
||||
* @param options
|
||||
* @returns global version with specified ID
|
||||
*/
|
||||
findGlobalVersionByID = async <T extends TypeWithVersion<T> = any>(options: FindGlobalVersionByIDOptions): Promise<T> => {
|
||||
const { findVersionByID } = localGlobalOperations;
|
||||
return findVersionByID(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Restore global version by ID
|
||||
* @param options
|
||||
* @returns version with specified ID
|
||||
*/
|
||||
restoreGlobalVersion = async <T extends TypeWithVersion<T> = any>(options: RestoreGlobalVersionOptions): Promise<T> => {
|
||||
const { restoreVersion } = localGlobalOperations;
|
||||
return restoreVersion(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find document by ID
|
||||
* @param options
|
||||
* @returns document with specified ID
|
||||
*/
|
||||
findByID = async <T extends TypeWithID = any>(options: FindByIDOptions): Promise<T> => {
|
||||
const { findByID } = localOperations;
|
||||
return findByID<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Update document
|
||||
* @param options
|
||||
* @returns Updated document
|
||||
*/
|
||||
update = async <T = any>(options: UpdateOptions<T>): Promise<T> => {
|
||||
const { update } = localOperations;
|
||||
return update<T>(this, options);
|
||||
}
|
||||
|
||||
delete = async <T extends TypeWithID = any>(options: DeleteOptions): Promise<T> => {
|
||||
const { localDelete } = localOperations;
|
||||
return localDelete<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find versions with criteria
|
||||
* @param options
|
||||
* @returns versions satisfying query
|
||||
*/
|
||||
findVersions = async <T extends TypeWithVersion<T> = any>(options: FindVersionsOptions): Promise<PaginatedDocs<T>> => {
|
||||
const { findVersions } = localOperations;
|
||||
return findVersions<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find version by ID
|
||||
* @param options
|
||||
* @returns version with specified ID
|
||||
*/
|
||||
findVersionByID = async <T extends TypeWithVersion<T> = any>(options: FindVersionByIDOptions): Promise<T> => {
|
||||
const { findVersionByID } = localOperations;
|
||||
return findVersionByID(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Restore version by ID
|
||||
* @param options
|
||||
* @returns version with specified ID
|
||||
*/
|
||||
restoreVersion = async <T extends TypeWithVersion<T> = any>(options: RestoreVersionOptions): Promise<T> => {
|
||||
const { restoreVersion } = localOperations;
|
||||
return restoreVersion(this, options);
|
||||
}
|
||||
|
||||
login = async <T extends TypeWithID = any>(options: LoginOptions): Promise<LoginResult & { user: T }> => {
|
||||
const { login } = localOperations.auth;
|
||||
return login(this, options);
|
||||
}
|
||||
|
||||
forgotPassword = async (options: ForgotPasswordOptions): Promise<ForgotPasswordResult> => {
|
||||
const { forgotPassword } = localOperations.auth;
|
||||
return forgotPassword(this, options);
|
||||
}
|
||||
|
||||
resetPassword = async (options: ResetPasswordOptions): Promise<ResetPasswordResult> => {
|
||||
const { resetPassword } = localOperations.auth;
|
||||
return resetPassword(this, options);
|
||||
}
|
||||
|
||||
unlock = async (options: UnlockOptions): Promise<boolean> => {
|
||||
const { unlock } = localOperations.auth;
|
||||
return unlock(this, options);
|
||||
}
|
||||
|
||||
verifyEmail = async (options: VerifyEmailOptions): Promise<boolean> => {
|
||||
const { verifyEmail } = localOperations.auth;
|
||||
return verifyEmail(this, options);
|
||||
export class PayloadHTTP extends BasePayload<GeneratedTypes> {
|
||||
async init(options: InitOptions): Promise<Payload> {
|
||||
const payload = await initHTTP(options);
|
||||
Object.assign(this, payload);
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
const payload = new Payload();
|
||||
const payload = new PayloadHTTP();
|
||||
|
||||
export default payload;
|
||||
module.exports = payload;
|
||||
|
||||
178
src/init.ts
178
src/init.ts
@@ -1,178 +0,0 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import express, { NextFunction, Response } from 'express';
|
||||
import crypto from 'crypto';
|
||||
import path from 'path';
|
||||
import mongoose from 'mongoose';
|
||||
import { InitOptions } from './config/types';
|
||||
|
||||
import authenticate from './express/middleware/authenticate';
|
||||
import connectMongoose from './mongoose/connect';
|
||||
import expressMiddleware from './express/middleware';
|
||||
import initAdmin from './express/admin';
|
||||
import initAuth from './auth/init';
|
||||
import access from './auth/requestHandlers/access';
|
||||
import initCollections from './collections/init';
|
||||
import initPreferences from './preferences/init';
|
||||
import initGlobals from './globals/init';
|
||||
import initGraphQLPlayground from './graphql/initPlayground';
|
||||
import initStatic from './express/static';
|
||||
import registerSchema from './graphql/registerSchema';
|
||||
import graphQLHandler from './graphql/graphQLHandler';
|
||||
import buildEmail from './email/build';
|
||||
import identifyAPI from './express/middleware/identifyAPI';
|
||||
import errorHandler from './express/middleware/errorHandler';
|
||||
import { PayloadRequest } from './express/types';
|
||||
import sendEmail from './email/sendEmail';
|
||||
|
||||
import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/serverInit';
|
||||
import { Payload } from '.';
|
||||
import loadConfig from './config/load';
|
||||
import Logger from './utilities/logger';
|
||||
import { getDataLoader } from './collections/dataloader';
|
||||
import mountEndpoints from './express/mountEndpoints';
|
||||
import PreferencesModel from './preferences/model';
|
||||
import findConfig from './config/find';
|
||||
|
||||
export const init = (payload: Payload, options: InitOptions): void => {
|
||||
payload.logger.info('Starting Payload...');
|
||||
if (!options.secret) {
|
||||
throw new Error(
|
||||
'Error: missing secret key. A secret key is needed to secure Payload.',
|
||||
);
|
||||
}
|
||||
|
||||
if (options.mongoURL !== false && typeof options.mongoURL !== 'string') {
|
||||
throw new Error('Error: missing MongoDB connection URL.');
|
||||
}
|
||||
|
||||
payload.emailOptions = { ...(options.email) };
|
||||
payload.secret = crypto
|
||||
.createHash('sha256')
|
||||
.update(options.secret)
|
||||
.digest('hex')
|
||||
.slice(0, 32);
|
||||
|
||||
payload.local = options.local;
|
||||
|
||||
if (options.config) {
|
||||
payload.config = options.config;
|
||||
const configPath = findConfig();
|
||||
|
||||
payload.config = {
|
||||
...options.config,
|
||||
paths: {
|
||||
configDir: path.dirname(configPath),
|
||||
config: configPath,
|
||||
rawConfig: configPath,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
payload.config = loadConfig(payload.logger);
|
||||
}
|
||||
|
||||
// If not initializing locally, scaffold router
|
||||
if (!payload.local) {
|
||||
payload.router = express.Router();
|
||||
payload.router.use(...expressMiddleware(payload));
|
||||
initAuth(payload);
|
||||
}
|
||||
|
||||
// Configure email service
|
||||
payload.email = buildEmail(payload.emailOptions, payload.logger);
|
||||
payload.sendEmail = sendEmail.bind(payload);
|
||||
|
||||
// Initialize collections & globals
|
||||
initCollections(payload);
|
||||
initGlobals(payload);
|
||||
|
||||
if (!payload.config.graphQL.disable) {
|
||||
registerSchema(payload);
|
||||
}
|
||||
|
||||
payload.preferences = { Model: PreferencesModel };
|
||||
|
||||
// If not initializing locally, set up HTTP routing
|
||||
if (!payload.local) {
|
||||
options.express.use((req: PayloadRequest, res, next) => {
|
||||
req.payload = payload;
|
||||
next();
|
||||
});
|
||||
|
||||
options.express.use((req: PayloadRequest, res: Response, next: NextFunction): void => {
|
||||
req.payloadDataLoader = getDataLoader(req);
|
||||
return next();
|
||||
});
|
||||
|
||||
payload.express = options.express;
|
||||
|
||||
if (payload.config.rateLimit.trustProxy) {
|
||||
payload.express.set('trust proxy', 1);
|
||||
}
|
||||
|
||||
initAdmin(payload);
|
||||
initPreferences(payload);
|
||||
|
||||
payload.router.get('/access', access);
|
||||
|
||||
if (!payload.config.graphQL.disable) {
|
||||
payload.router.use(
|
||||
payload.config.routes.graphQL,
|
||||
(req, res, next): void => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.sendStatus(204);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
identifyAPI('GraphQL'),
|
||||
(req: PayloadRequest, res: Response) => graphQLHandler(req, res)(req, res),
|
||||
);
|
||||
initGraphQLPlayground(payload);
|
||||
}
|
||||
|
||||
mountEndpoints(options.express, payload.router, payload.config.endpoints);
|
||||
|
||||
// Bind router to API
|
||||
payload.express.use(payload.config.routes.api, payload.router);
|
||||
|
||||
// Enable static routes for all collections permitting upload
|
||||
initStatic(payload);
|
||||
|
||||
payload.errorHandler = errorHandler(payload.config, payload.logger);
|
||||
payload.router.use(payload.errorHandler);
|
||||
|
||||
payload.authenticate = authenticate(payload.config);
|
||||
}
|
||||
|
||||
serverInitTelemetry(payload);
|
||||
};
|
||||
|
||||
export const initAsync = async (payload: Payload, options: InitOptions): Promise<void> => {
|
||||
payload.logger = Logger('payload', options.loggerOptions);
|
||||
payload.mongoURL = options.mongoURL;
|
||||
|
||||
if (payload.mongoURL) {
|
||||
mongoose.set('strictQuery', false);
|
||||
payload.mongoMemoryServer = await connectMongoose(payload.mongoURL, options.mongoOptions, payload.logger);
|
||||
}
|
||||
|
||||
init(payload, options);
|
||||
|
||||
if (typeof options.onInit === 'function') await options.onInit(payload);
|
||||
if (typeof payload.config.onInit === 'function') await payload.config.onInit(payload);
|
||||
};
|
||||
|
||||
export const initSync = (payload: Payload, options: InitOptions): void => {
|
||||
payload.logger = Logger('payload', options.loggerOptions);
|
||||
payload.mongoURL = options.mongoURL;
|
||||
|
||||
if (payload.mongoURL) {
|
||||
mongoose.set('strictQuery', false);
|
||||
connectMongoose(payload.mongoURL, options.mongoOptions, payload.logger);
|
||||
}
|
||||
|
||||
init(payload, options);
|
||||
|
||||
if (typeof options.onInit === 'function') options.onInit(payload);
|
||||
if (typeof payload.config.onInit === 'function') payload.config.onInit(payload);
|
||||
};
|
||||
87
src/initHTTP.ts
Normal file
87
src/initHTTP.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import express, { NextFunction, Response } from 'express';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import { InitOptions } from './config/types';
|
||||
|
||||
import authenticate from './express/middleware/authenticate';
|
||||
import expressMiddleware from './express/middleware';
|
||||
import initAdmin from './express/admin';
|
||||
import initAuth from './auth/init';
|
||||
import access from './auth/requestHandlers/access';
|
||||
import initCollectionsHTTP from './collections/initHTTP';
|
||||
import initPreferences from './preferences/init';
|
||||
import initGlobalsHTTP from './globals/initHTTP';
|
||||
import initGraphQLPlayground from './graphql/initPlayground';
|
||||
import initStatic from './express/static';
|
||||
import graphQLHandler from './graphql/graphQLHandler';
|
||||
import identifyAPI from './express/middleware/identifyAPI';
|
||||
import errorHandler from './express/middleware/errorHandler';
|
||||
import { PayloadRequest } from './express/types';
|
||||
import { getDataLoader } from './collections/dataloader';
|
||||
import mountEndpoints from './express/mountEndpoints';
|
||||
import { Payload, getPayload } from './payload';
|
||||
|
||||
export const initHTTP = async (options: InitOptions): Promise<Payload> => {
|
||||
const payload = await getPayload(options);
|
||||
|
||||
if (!options.local) {
|
||||
payload.router = express.Router();
|
||||
payload.router.use(...expressMiddleware(payload));
|
||||
initAuth(payload);
|
||||
|
||||
initCollectionsHTTP(payload);
|
||||
initGlobalsHTTP(payload);
|
||||
|
||||
options.express.use((req: PayloadRequest, res, next) => {
|
||||
req.payload = payload;
|
||||
next();
|
||||
});
|
||||
|
||||
options.express.use((req: PayloadRequest, res: Response, next: NextFunction): void => {
|
||||
req.payloadDataLoader = getDataLoader(req);
|
||||
return next();
|
||||
});
|
||||
|
||||
payload.express = options.express;
|
||||
|
||||
if (payload.config.rateLimit.trustProxy) {
|
||||
payload.express.set('trust proxy', 1);
|
||||
}
|
||||
|
||||
initAdmin(payload);
|
||||
initPreferences(payload);
|
||||
|
||||
payload.router.get('/access', access);
|
||||
|
||||
if (!payload.config.graphQL.disable) {
|
||||
payload.router.use(
|
||||
payload.config.routes.graphQL,
|
||||
(req, res, next): void => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.sendStatus(204);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
identifyAPI('GraphQL'),
|
||||
(req: PayloadRequest, res: Response) => graphQLHandler(req, res)(req, res),
|
||||
);
|
||||
initGraphQLPlayground(payload);
|
||||
}
|
||||
|
||||
mountEndpoints(options.express, payload.router, payload.config.endpoints);
|
||||
|
||||
// Bind router to API
|
||||
payload.express.use(payload.config.routes.api, payload.router);
|
||||
|
||||
// Enable static routes for all collections permitting upload
|
||||
initStatic(payload);
|
||||
|
||||
payload.errorHandler = errorHandler(payload.config, payload.logger);
|
||||
payload.router.use(payload.errorHandler);
|
||||
|
||||
payload.authenticate = authenticate(payload.config);
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
412
src/payload.ts
Normal file
412
src/payload.ts
Normal file
@@ -0,0 +1,412 @@
|
||||
import pino from 'pino';
|
||||
import type { Express, Router } from 'express';
|
||||
import { GraphQLError, GraphQLFormattedError, GraphQLSchema } from 'graphql';
|
||||
import crypto from 'crypto';
|
||||
import path from 'path';
|
||||
import mongoose from 'mongoose';
|
||||
import { Config as GeneratedTypes } from 'payload/generated-types';
|
||||
import {
|
||||
Collection,
|
||||
CollectionModel,
|
||||
} from './collections/config/types';
|
||||
import {
|
||||
SanitizedConfig,
|
||||
EmailOptions,
|
||||
InitOptions,
|
||||
} from './config/types';
|
||||
import { TypeWithVersion } from './versions/types';
|
||||
import { PaginatedDocs } from './mongoose/types';
|
||||
|
||||
import { PayloadAuthenticate } from './express/middleware/authenticate';
|
||||
import { Globals } from './globals/config/types';
|
||||
import { ErrorHandler } from './express/middleware/errorHandler';
|
||||
import localOperations from './collections/operations/local';
|
||||
import localGlobalOperations from './globals/operations/local';
|
||||
import { encrypt, decrypt } from './auth/crypto';
|
||||
import { BuildEmailResult, Message } from './email/types';
|
||||
import { Preferences } from './preferences/types';
|
||||
|
||||
import { Options as CreateOptions } from './collections/operations/local/create';
|
||||
import { Options as FindOptions } from './collections/operations/local/find';
|
||||
import { Options as FindByIDOptions } from './collections/operations/local/findByID';
|
||||
import { Options as UpdateOptions } from './collections/operations/local/update';
|
||||
import { Options as DeleteOptions } from './collections/operations/local/delete';
|
||||
import { Options as FindVersionsOptions } from './collections/operations/local/findVersions';
|
||||
import { Options as FindVersionByIDOptions } from './collections/operations/local/findVersionByID';
|
||||
import { Options as RestoreVersionOptions } from './collections/operations/local/restoreVersion';
|
||||
import { Options as FindGlobalVersionsOptions } from './globals/operations/local/findVersions';
|
||||
import { Options as FindGlobalVersionByIDOptions } from './globals/operations/local/findVersionByID';
|
||||
import { Options as RestoreGlobalVersionOptions } from './globals/operations/local/restoreVersion';
|
||||
import { Options as ForgotPasswordOptions } from './auth/operations/local/forgotPassword';
|
||||
import { Options as LoginOptions } from './auth/operations/local/login';
|
||||
import { Options as ResetPasswordOptions } from './auth/operations/local/resetPassword';
|
||||
import { Options as UnlockOptions } from './auth/operations/local/unlock';
|
||||
import { Options as VerifyEmailOptions } from './auth/operations/local/verifyEmail';
|
||||
import { Result as ForgotPasswordResult } from './auth/operations/forgotPassword';
|
||||
import { Result as ResetPasswordResult } from './auth/operations/resetPassword';
|
||||
import { Result as LoginResult } from './auth/operations/login';
|
||||
import { Options as FindGlobalOptions } from './globals/operations/local/findOne';
|
||||
import { Options as UpdateGlobalOptions } from './globals/operations/local/update';
|
||||
|
||||
import connectMongoose from './mongoose/connect';
|
||||
import initCollections from './collections/initLocal';
|
||||
import initGlobals from './globals/initLocal';
|
||||
import registerSchema from './graphql/registerSchema';
|
||||
import buildEmail from './email/build';
|
||||
import sendEmail from './email/sendEmail';
|
||||
|
||||
import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/serverInit';
|
||||
import loadConfig from './config/load';
|
||||
import Logger from './utilities/logger';
|
||||
import PreferencesModel from './preferences/model';
|
||||
import findConfig from './config/find';
|
||||
|
||||
/**
|
||||
* @description Payload
|
||||
*/
|
||||
export class BasePayload<TGeneratedTypes extends GeneratedTypes> {
|
||||
config: SanitizedConfig;
|
||||
|
||||
collections: {
|
||||
[slug: string | number | symbol]: Collection;
|
||||
} = {}
|
||||
|
||||
versions: {
|
||||
[slug: string]: CollectionModel;
|
||||
} = {}
|
||||
|
||||
preferences: Preferences;
|
||||
|
||||
globals: Globals;
|
||||
|
||||
logger: pino.Logger;
|
||||
|
||||
emailOptions: EmailOptions;
|
||||
|
||||
email: BuildEmailResult;
|
||||
|
||||
sendEmail: (message: Message) => Promise<unknown>;
|
||||
|
||||
secret: string;
|
||||
|
||||
mongoURL: string | false;
|
||||
|
||||
mongoMemoryServer: any
|
||||
|
||||
local: boolean;
|
||||
|
||||
encrypt = encrypt;
|
||||
|
||||
decrypt = decrypt;
|
||||
|
||||
errorHandler: ErrorHandler;
|
||||
|
||||
authenticate: PayloadAuthenticate;
|
||||
|
||||
express?: Express
|
||||
|
||||
router?: Router
|
||||
|
||||
types: {
|
||||
blockTypes: any;
|
||||
blockInputTypes: any;
|
||||
localeInputType?: any;
|
||||
fallbackLocaleInputType?: any;
|
||||
};
|
||||
|
||||
Query: { name: string; fields: { [key: string]: any } } = { name: 'Query', fields: {} };
|
||||
|
||||
Mutation: { name: string; fields: { [key: string]: any } } = { name: 'Mutation', fields: {} };
|
||||
|
||||
schema: GraphQLSchema;
|
||||
|
||||
extensions: (info: any) => Promise<any>;
|
||||
|
||||
customFormatErrorFn: (error: GraphQLError) => GraphQLFormattedError;
|
||||
|
||||
validationRules: any;
|
||||
|
||||
errorResponses: GraphQLFormattedError[] = [];
|
||||
|
||||
errorIndex: number;
|
||||
|
||||
getAdminURL = (): string => `${this.config.serverURL}${this.config.routes.admin}`;
|
||||
|
||||
getAPIURL = (): string => `${this.config.serverURL}${this.config.routes.api}`;
|
||||
|
||||
/**
|
||||
* @description Initializes Payload
|
||||
* @param options
|
||||
*/
|
||||
async init(options: InitOptions): Promise<Payload> {
|
||||
this.logger = Logger('payload', options.loggerOptions);
|
||||
this.mongoURL = options.mongoURL;
|
||||
|
||||
if (this.mongoURL) {
|
||||
mongoose.set('strictQuery', false);
|
||||
this.mongoMemoryServer = await connectMongoose(this.mongoURL, options.mongoOptions, this.logger);
|
||||
}
|
||||
|
||||
this.logger.info('Starting Payload...');
|
||||
if (!options.secret) {
|
||||
throw new Error(
|
||||
'Error: missing secret key. A secret key is needed to secure Payload.',
|
||||
);
|
||||
}
|
||||
|
||||
if (options.mongoURL !== false && typeof options.mongoURL !== 'string') {
|
||||
throw new Error('Error: missing MongoDB connection URL.');
|
||||
}
|
||||
|
||||
this.emailOptions = { ...(options.email) };
|
||||
this.secret = crypto
|
||||
.createHash('sha256')
|
||||
.update(options.secret)
|
||||
.digest('hex')
|
||||
.slice(0, 32);
|
||||
|
||||
this.local = options.local;
|
||||
|
||||
if (options.config) {
|
||||
this.config = options.config;
|
||||
const configPath = findConfig();
|
||||
|
||||
this.config = {
|
||||
...options.config,
|
||||
paths: {
|
||||
configDir: path.dirname(configPath),
|
||||
config: configPath,
|
||||
rawConfig: configPath,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
this.config = loadConfig(this.logger);
|
||||
}
|
||||
|
||||
// Configure email service
|
||||
this.email = buildEmail(this.emailOptions, this.logger);
|
||||
this.sendEmail = sendEmail.bind(this);
|
||||
|
||||
// Initialize collections & globals
|
||||
initCollections(this);
|
||||
initGlobals(this);
|
||||
|
||||
if (!this.config.graphQL.disable) {
|
||||
registerSchema(this);
|
||||
}
|
||||
|
||||
this.preferences = { Model: PreferencesModel };
|
||||
|
||||
serverInitTelemetry(this);
|
||||
|
||||
if (typeof options.onInit === 'function') await options.onInit(this);
|
||||
if (typeof this.config.onInit === 'function') await this.config.onInit(this);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Performs create operation
|
||||
* @param options
|
||||
* @returns created document
|
||||
*/
|
||||
create = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: CreateOptions<T>,
|
||||
): Promise<TGeneratedTypes['collections'][T]> => {
|
||||
const { create } = localOperations;
|
||||
return create<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find documents with criteria
|
||||
* @param options
|
||||
* @returns documents satisfying query
|
||||
*/
|
||||
find = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: FindOptions<T>,
|
||||
): Promise<PaginatedDocs<TGeneratedTypes['collections'][T]>> => {
|
||||
const { find } = localOperations;
|
||||
return find<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find document by ID
|
||||
* @param options
|
||||
* @returns document with specified ID
|
||||
*/
|
||||
|
||||
findByID = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: FindByIDOptions<T>,
|
||||
): Promise<TGeneratedTypes['collections'][T]> => {
|
||||
const { findByID } = localOperations;
|
||||
return findByID<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Update document
|
||||
* @param options
|
||||
* @returns Updated document
|
||||
*/
|
||||
update = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: UpdateOptions<T>,
|
||||
): Promise<TGeneratedTypes['collections'][T]> => {
|
||||
const { update } = localOperations;
|
||||
return update<T>(this, options);
|
||||
}
|
||||
|
||||
delete = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: DeleteOptions<T>,
|
||||
): Promise<TGeneratedTypes['collections'][T]> => {
|
||||
const { localDelete } = localOperations;
|
||||
return localDelete<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find versions with criteria
|
||||
* @param options
|
||||
* @returns versions satisfying query
|
||||
*/
|
||||
findVersions = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: FindVersionsOptions<T>,
|
||||
): Promise<PaginatedDocs<TypeWithVersion<TGeneratedTypes['collections'][T]>>> => {
|
||||
const { findVersions } = localOperations;
|
||||
return findVersions<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find version by ID
|
||||
* @param options
|
||||
* @returns version with specified ID
|
||||
*/
|
||||
findVersionByID = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: FindVersionByIDOptions<T>,
|
||||
): Promise<TypeWithVersion<TGeneratedTypes['collections'][T]>> => {
|
||||
const { findVersionByID } = localOperations;
|
||||
return findVersionByID<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Restore version by ID
|
||||
* @param options
|
||||
* @returns version with specified ID
|
||||
*/
|
||||
restoreVersion = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: RestoreVersionOptions<T>,
|
||||
): Promise<TGeneratedTypes['collections'][T]> => {
|
||||
const { restoreVersion } = localOperations;
|
||||
return restoreVersion<T>(this, options);
|
||||
}
|
||||
|
||||
login = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: LoginOptions<T>,
|
||||
): Promise<LoginResult & { user: TGeneratedTypes['collections'][T] }> => {
|
||||
const { login } = localOperations.auth;
|
||||
return login<T>(this, options);
|
||||
}
|
||||
|
||||
forgotPassword = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: ForgotPasswordOptions<T>,
|
||||
): Promise<ForgotPasswordResult> => {
|
||||
const { forgotPassword } = localOperations.auth;
|
||||
return forgotPassword<T>(this, options);
|
||||
}
|
||||
|
||||
resetPassword = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: ResetPasswordOptions<T>,
|
||||
): Promise<ResetPasswordResult> => {
|
||||
const { resetPassword } = localOperations.auth;
|
||||
return resetPassword<T>(this, options);
|
||||
}
|
||||
|
||||
unlock = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: UnlockOptions<T>,
|
||||
): Promise<boolean> => {
|
||||
const { unlock } = localOperations.auth;
|
||||
return unlock(this, options);
|
||||
}
|
||||
|
||||
verifyEmail = async <T extends keyof TGeneratedTypes['collections']>(
|
||||
options: VerifyEmailOptions<T>,
|
||||
): Promise<boolean> => {
|
||||
const { verifyEmail } = localOperations.auth;
|
||||
return verifyEmail(this, options);
|
||||
}
|
||||
|
||||
findGlobal = async <T extends keyof TGeneratedTypes['globals']>(
|
||||
options: FindGlobalOptions<T>,
|
||||
): Promise<TGeneratedTypes['globals'][T]> => {
|
||||
const { findOne } = localGlobalOperations;
|
||||
return findOne<T>(this, options);
|
||||
}
|
||||
|
||||
updateGlobal = async <T extends keyof TGeneratedTypes['globals']>(
|
||||
options: UpdateGlobalOptions<T>,
|
||||
): Promise<TGeneratedTypes['globals'][T]> => {
|
||||
const { update } = localGlobalOperations;
|
||||
return update<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find global versions with criteria
|
||||
* @param options
|
||||
* @returns versions satisfying query
|
||||
*/
|
||||
findGlobalVersions = async <T extends keyof TGeneratedTypes['globals']>(
|
||||
options: FindGlobalVersionsOptions<T>,
|
||||
): Promise<PaginatedDocs<TypeWithVersion<TGeneratedTypes['globals'][T]>>> => {
|
||||
const { findVersions } = localGlobalOperations;
|
||||
return findVersions<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Find global version by ID
|
||||
* @param options
|
||||
* @returns global version with specified ID
|
||||
*/
|
||||
findGlobalVersionByID = async <T extends keyof TGeneratedTypes['globals']>(
|
||||
options: FindGlobalVersionByIDOptions<T>,
|
||||
): Promise<TypeWithVersion<TGeneratedTypes['globals'][T]>> => {
|
||||
const { findVersionByID } = localGlobalOperations;
|
||||
return findVersionByID<T>(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Restore global version by ID
|
||||
* @param options
|
||||
* @returns version with specified ID
|
||||
*/
|
||||
restoreGlobalVersion = async <T extends keyof TGeneratedTypes['globals']>(
|
||||
options: RestoreGlobalVersionOptions<T>,
|
||||
): Promise<TGeneratedTypes['globals'][T]> => {
|
||||
const { restoreVersion } = localGlobalOperations;
|
||||
return restoreVersion<T>(this, options);
|
||||
}
|
||||
}
|
||||
|
||||
export type Payload = BasePayload<GeneratedTypes>
|
||||
|
||||
let cached = global._payload;
|
||||
|
||||
if (!cached) {
|
||||
// eslint-disable-next-line no-multi-assign
|
||||
cached = global._payload = { payload: null, promise: null };
|
||||
}
|
||||
|
||||
export const getPayload = async (options: InitOptions): Promise<Payload> => {
|
||||
if (cached.payload) {
|
||||
return cached.payload;
|
||||
}
|
||||
|
||||
if (!cached.promise) {
|
||||
cached.promise = new BasePayload<GeneratedTypes>().init(options);
|
||||
}
|
||||
|
||||
try {
|
||||
cached.payload = await cached.promise;
|
||||
} catch (e) {
|
||||
cached.promise = null;
|
||||
throw e;
|
||||
}
|
||||
|
||||
return cached.payload;
|
||||
};
|
||||
@@ -9,7 +9,7 @@ import { DateTimeResolver } from 'graphql-scalars';
|
||||
import findOne from '../operations/findOne';
|
||||
import update from '../operations/update';
|
||||
import deleteOperation from '../operations/delete';
|
||||
import { Payload } from '../..';
|
||||
import { Payload } from '../../payload';
|
||||
|
||||
function initCollectionsGraphQL(payload: Payload): void {
|
||||
const valueType = GraphQLJSON;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import express from 'express';
|
||||
|
||||
import { Payload } from '../index';
|
||||
import findOne from './requestHandlers/findOne';
|
||||
import update from './requestHandlers/update';
|
||||
import deleteHandler from './requestHandlers/delete';
|
||||
import { Payload } from '../payload';
|
||||
|
||||
export default function initPreferences(ctx: Payload): void {
|
||||
if (!ctx.local) {
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
"block": "blok",
|
||||
"blocks": "blokken",
|
||||
"addLabel": "Voeg {{label}} toe",
|
||||
"addLink": "Voeg een link tsoe",
|
||||
"addLink": "Voeg een link toe",
|
||||
"addNew": "Nieuw(e)",
|
||||
"addNewLabel": "Nieuw(e) {{label}} toevoegen",
|
||||
"addRelationship": "Nieuwe Relatie",
|
||||
@@ -112,7 +112,7 @@
|
||||
"passwordsDoNotMatch": "Wachtwoorden komen niet overeen.",
|
||||
"relatedDocument": "Gerelateerd document",
|
||||
"relationTo": "Relatie tot",
|
||||
"removeRelationship": "Relatie Vserwijderen",
|
||||
"removeRelationship": "Relatie Verwijderen",
|
||||
"removeUpload": "Verwijder Upload",
|
||||
"saveChanges": "Bewaar aanpassingen",
|
||||
"searchForBlock": "Zoeken naar een blok",
|
||||
|
||||
@@ -14,21 +14,21 @@ import { FileData, FileToSave } from './types';
|
||||
import canResizeImage from './canResizeImage';
|
||||
import isImage from './isImage';
|
||||
|
||||
type Args = {
|
||||
type Args<T> = {
|
||||
config: SanitizedConfig,
|
||||
collection: Collection
|
||||
throwOnMissingFile?: boolean
|
||||
req: PayloadRequest
|
||||
data: Record<string, unknown>
|
||||
data: T
|
||||
overwriteExistingFiles?: boolean
|
||||
}
|
||||
|
||||
type Result = Promise<{
|
||||
data: Record<string, unknown>
|
||||
type Result<T> = Promise<{
|
||||
data: T
|
||||
files: FileToSave[]
|
||||
}>
|
||||
|
||||
export const generateFileData = async ({
|
||||
export const generateFileData = async <T>({
|
||||
config,
|
||||
collection: {
|
||||
config: collectionConfig,
|
||||
@@ -38,7 +38,7 @@ export const generateFileData = async ({
|
||||
data,
|
||||
throwOnMissingFile,
|
||||
overwriteExistingFiles,
|
||||
}: Args): Result => {
|
||||
}: Args<T>): Result<T> => {
|
||||
let newData = data;
|
||||
const filesToSave: FileToSave[] = [];
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TFunction } from 'i18next';
|
||||
import { FileUploadError } from '../errors';
|
||||
import saveBufferToFile from './saveBufferToFile';
|
||||
import { FileToSave } from './types';
|
||||
import { Payload } from '..';
|
||||
import { Payload } from '../payload';
|
||||
|
||||
export const uploadFiles = async (payload: Payload, files: FileToSave[], t: TFunction): Promise<void> => {
|
||||
try {
|
||||
|
||||
@@ -3,10 +3,14 @@ import memoize from 'micro-memoize';
|
||||
|
||||
export type PayloadLogger = pino.Logger;
|
||||
|
||||
const defaultLoggerOptions = {
|
||||
prettyPrint: {
|
||||
ignore: 'pid,hostname',
|
||||
translateTime: 'HH:MM:ss',
|
||||
const defaultLoggerOptions: pino.LoggerOptions = {
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
ignore: 'pid,hostname',
|
||||
translateTime: 'HH:MM:ss',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { TypeWithID } from '../collections/config/types';
|
||||
|
||||
const internalFields = ['__v', 'salt', 'hash'];
|
||||
|
||||
const sanitizeInternalFields = <T extends TypeWithID = any>(incomingDoc): T => Object.entries(incomingDoc).reduce((newDoc, [key, val]): T => {
|
||||
const sanitizeInternalFields = <T extends Record<string, unknown>>(incomingDoc: T): T => Object.entries(incomingDoc).reduce((newDoc, [key, val]): T => {
|
||||
if (key === '_id') {
|
||||
return {
|
||||
...newDoc,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { sendEvent } from '..';
|
||||
import { Payload } from '../../..';
|
||||
import { Payload } from '../../../payload';
|
||||
|
||||
export type ServerInitEvent = {
|
||||
type: 'server-init'
|
||||
|
||||
@@ -3,7 +3,7 @@ import Conf from 'conf';
|
||||
import { randomBytes } from 'crypto';
|
||||
import findUp from 'find-up';
|
||||
import fs from 'fs';
|
||||
import { Payload } from '../../index';
|
||||
import { Payload } from '../../payload';
|
||||
import { ServerInitEvent } from './events/serverInit';
|
||||
import { AdminInitEvent } from './events/adminInit';
|
||||
import { oneWayHash } from './oneWayHash';
|
||||
@@ -28,7 +28,7 @@ type Args = {
|
||||
event: TelemetryEvent
|
||||
}
|
||||
|
||||
export const sendEvent = async ({ payload, event } : Args): Promise<void> => {
|
||||
export const sendEvent = async ({ payload, event }: Args): Promise<void> => {
|
||||
if (payload.config.telemetry !== false) {
|
||||
try {
|
||||
const packageJSON = await getPackageJSON();
|
||||
@@ -49,7 +49,7 @@ export const sendEvent = async ({ payload, event } : Args): Promise<void> => {
|
||||
body: JSON.stringify({ ...baseEvent, ...event }),
|
||||
});
|
||||
} catch (_) {
|
||||
// Eat any errors in sending telemetry event
|
||||
// Eat any errors in sending telemetry event
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,6 +14,20 @@ export const buildVersionCollectionFields = (collection: SanitizedCollectionConf
|
||||
type: 'group',
|
||||
fields: collection.fields,
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
type: 'date',
|
||||
admin: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
type: 'date',
|
||||
admin: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (collection?.versions?.drafts && collection?.versions?.drafts?.autosave) {
|
||||
|
||||
@@ -8,6 +8,20 @@ export const buildVersionGlobalFields = (global: SanitizedGlobalConfig): Field[]
|
||||
type: 'group',
|
||||
fields: global.fields,
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
type: 'date',
|
||||
admin: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
type: 'date',
|
||||
admin: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (global?.versions?.drafts && global?.versions?.drafts?.autosave) {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Payload } from '..';
|
||||
import { SanitizedCollectionConfig } from '../collections/config/types';
|
||||
import { SanitizedGlobalConfig } from '../globals/config/types';
|
||||
import { TypeWithVersion } from './types';
|
||||
|
||||
type Args = {
|
||||
payload: Payload,
|
||||
entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig,
|
||||
version: TypeWithVersion<any>
|
||||
}
|
||||
|
||||
const cleanUpFailedVersion = (args: Args) => {
|
||||
const { payload, entityConfig, version } = args;
|
||||
|
||||
if (version) {
|
||||
const VersionModel = payload.versions[entityConfig.slug];
|
||||
VersionModel.findOneAndDelete({ _id: version.id });
|
||||
}
|
||||
};
|
||||
|
||||
export default cleanUpFailedVersion;
|
||||
@@ -6,7 +6,7 @@ export const versionCollectionDefaults: IncomingCollectionVersions = {
|
||||
interval: 2000, // in milliseconds
|
||||
},
|
||||
},
|
||||
retainDeleted: true,
|
||||
retainDeleted: false,
|
||||
};
|
||||
|
||||
export const versionGlobalDefaults: IncomingGlobalVersions = {
|
||||
|
||||
25
src/versions/deleteCollectionVersions.ts
Normal file
25
src/versions/deleteCollectionVersions.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Payload } from '../payload';
|
||||
|
||||
type Args = {
|
||||
payload: Payload
|
||||
slug: string
|
||||
id?: string | number
|
||||
}
|
||||
|
||||
export const deleteCollectionVersions = async ({
|
||||
payload,
|
||||
slug,
|
||||
id,
|
||||
}: Args): Promise<void> => {
|
||||
const VersionsModel = payload.versions[slug];
|
||||
|
||||
try {
|
||||
await VersionsModel.deleteMany({
|
||||
parent: {
|
||||
$eq: id,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
payload.logger.error(`There was an error removing versions for the deleted ${slug} document with ID ${id}.`);
|
||||
}
|
||||
};
|
||||
@@ -1,229 +0,0 @@
|
||||
import { AccessResult } from '../../config/types';
|
||||
import { Where } from '../../types';
|
||||
import { Payload } from '../..';
|
||||
import { PaginatedDocs } from '../../mongoose/types';
|
||||
import { Collection, CollectionModel, TypeWithID } from '../../collections/config/types';
|
||||
import { hasWhereAccessResult } from '../../auth';
|
||||
import { appendVersionToQueryKey } from './appendVersionToQueryKey';
|
||||
import replaceWithDraftIfAvailable from './replaceWithDraftIfAvailable';
|
||||
|
||||
type AggregateVersion<T> = {
|
||||
_id: string
|
||||
version: T
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
type VersionCollectionMatchMap<T> = {
|
||||
[_id: string | number]: {
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
version: T
|
||||
}
|
||||
}
|
||||
|
||||
type Args = {
|
||||
accessResult: AccessResult
|
||||
collection: Collection
|
||||
locale: string
|
||||
paginationOptions: any
|
||||
payload: Payload
|
||||
query: Record<string, unknown>
|
||||
where: Where
|
||||
}
|
||||
|
||||
export const mergeDrafts = async <T extends TypeWithID>({
|
||||
accessResult,
|
||||
collection,
|
||||
locale,
|
||||
payload,
|
||||
paginationOptions,
|
||||
query,
|
||||
where: incomingWhere,
|
||||
}: Args): Promise<PaginatedDocs<T>> => {
|
||||
// Query the main collection for any IDs that match the query
|
||||
// Create object "map" for performant lookup
|
||||
const mainCollectionMatchMap = await collection.Model.find(query, { updatedAt: 1 }, { limit: paginationOptions.limit, sort: paginationOptions.sort })
|
||||
.lean().then((res) => res.reduce((map, { _id, updatedAt }) => {
|
||||
const newMap = map;
|
||||
newMap[_id] = updatedAt;
|
||||
return newMap;
|
||||
}, {}));
|
||||
|
||||
// Query the versions collection with a version-specific query
|
||||
const VersionModel = payload.versions[collection.config.slug] as CollectionModel;
|
||||
|
||||
const where = appendVersionToQueryKey(incomingWhere || {});
|
||||
|
||||
const versionQueryToBuild: { where: Where } = {
|
||||
where: {
|
||||
...where,
|
||||
and: [
|
||||
...where?.and || [],
|
||||
{
|
||||
'version._status': {
|
||||
equals: 'draft',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
if (hasWhereAccessResult(accessResult)) {
|
||||
const versionAccessResult = appendVersionToQueryKey(accessResult);
|
||||
versionQueryToBuild.where.and.push(versionAccessResult);
|
||||
}
|
||||
|
||||
const versionQuery = await VersionModel.buildQuery(versionQueryToBuild, locale);
|
||||
const includedParentIDs: (string | number)[] = [];
|
||||
|
||||
// Create version "map" for performant lookup
|
||||
// and in the same loop, check if there are matched versions without a matched parent
|
||||
// This means that the newer version's parent should appear in the main query.
|
||||
// To do so, add the version's parent ID into an explicit `includedIDs` array
|
||||
const versionCollectionMatchMap = await VersionModel.aggregate<AggregateVersion<T>>([
|
||||
{
|
||||
$sort: Object.entries(paginationOptions.sort).reduce((sort, [key, order]) => {
|
||||
return {
|
||||
...sort,
|
||||
[key]: order === 'asc' ? 1 : -1,
|
||||
};
|
||||
}, {}),
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: '$parent',
|
||||
versionID: { $first: '$_id' },
|
||||
version: { $first: '$version' },
|
||||
updatedAt: { $first: '$updatedAt' },
|
||||
createdAt: { $first: '$createdAt' },
|
||||
},
|
||||
},
|
||||
{
|
||||
$addFields: {
|
||||
id: {
|
||||
$toObjectId: '$_id',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: collection.config.slug,
|
||||
localField: 'id',
|
||||
foreignField: '_id',
|
||||
as: 'parent',
|
||||
},
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
parent: {
|
||||
$size: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ $match: versionQuery },
|
||||
{ $limit: paginationOptions.limit },
|
||||
]).then((res) => res.reduce<VersionCollectionMatchMap<T>>((map, { _id, updatedAt, createdAt, version }) => {
|
||||
const newMap = map;
|
||||
newMap[_id] = { version, updatedAt, createdAt };
|
||||
|
||||
const matchedParent = mainCollectionMatchMap[_id];
|
||||
if (!matchedParent) includedParentIDs.push(_id);
|
||||
return newMap;
|
||||
}, {}));
|
||||
|
||||
// Now we need to explicitly exclude any parent matches that have newer versions
|
||||
// which did NOT appear in the versions query
|
||||
const excludedParentIDs = await Promise.all(Object.entries(mainCollectionMatchMap).map(async ([parentDocID, parentDocUpdatedAt]) => {
|
||||
// If there is a matched version, and it's newer, this parent should remain
|
||||
if (versionCollectionMatchMap[parentDocID] && versionCollectionMatchMap[parentDocID].updatedAt > parentDocUpdatedAt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise, we need to check if there are newer versions present
|
||||
// that did not get returned from the versions query
|
||||
const versionsQuery = await VersionModel.find({
|
||||
updatedAt: {
|
||||
$gt: parentDocUpdatedAt,
|
||||
},
|
||||
parent: {
|
||||
$eq: parentDocID,
|
||||
},
|
||||
}, {}, { limit: 1 }).lean();
|
||||
|
||||
// If there are,
|
||||
// this says that the newest version does not match the incoming query,
|
||||
// and the parent ID should be excluded
|
||||
if (versionsQuery.length > 0) {
|
||||
return parentDocID;
|
||||
}
|
||||
|
||||
return null;
|
||||
})).then((res) => res.filter((result) => Boolean(result)));
|
||||
|
||||
// Run a final query against the main collection,
|
||||
// passing in any ids to exclude and include
|
||||
// so that they appear properly paginated
|
||||
const finalQueryToBuild: { where: Where } = {
|
||||
where: {
|
||||
and: [],
|
||||
},
|
||||
};
|
||||
|
||||
finalQueryToBuild.where.and.push({ or: [] });
|
||||
|
||||
if (hasWhereAccessResult(accessResult)) {
|
||||
finalQueryToBuild.where.and.push(accessResult);
|
||||
}
|
||||
|
||||
if (incomingWhere) {
|
||||
finalQueryToBuild.where.and[0].or.push(incomingWhere);
|
||||
}
|
||||
|
||||
if (includedParentIDs.length > 0) {
|
||||
finalQueryToBuild.where.and[0].or.push({
|
||||
id: {
|
||||
in: includedParentIDs,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (excludedParentIDs.length > 0) {
|
||||
finalQueryToBuild.where.and.push({
|
||||
id: {
|
||||
not_in: excludedParentIDs,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const finalQuery = await collection.Model.buildQuery(finalQueryToBuild, locale);
|
||||
|
||||
let result = await collection.Model.paginate(finalQuery, paginationOptions);
|
||||
|
||||
result = {
|
||||
...result,
|
||||
docs: await Promise.all(result.docs.map(async (doc) => {
|
||||
const matchedVersion = versionCollectionMatchMap[doc.id];
|
||||
|
||||
if (matchedVersion && matchedVersion.updatedAt > doc.updatedAt) {
|
||||
return {
|
||||
...doc,
|
||||
...matchedVersion.version,
|
||||
createdAt: matchedVersion.createdAt,
|
||||
updatedAt: matchedVersion.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
return replaceWithDraftIfAvailable({
|
||||
accessResult,
|
||||
payload,
|
||||
entity: collection.config,
|
||||
entityType: 'collection',
|
||||
doc,
|
||||
locale,
|
||||
});
|
||||
})),
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
91
src/versions/drafts/queryDrafts.ts
Normal file
91
src/versions/drafts/queryDrafts.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { AccessResult } from '../../config/types';
|
||||
import { Where } from '../../types';
|
||||
import { Payload } from '../../payload';
|
||||
import { PaginatedDocs } from '../../mongoose/types';
|
||||
import { Collection, CollectionModel, TypeWithID } from '../../collections/config/types';
|
||||
import { hasWhereAccessResult } from '../../auth';
|
||||
import { appendVersionToQueryKey } from './appendVersionToQueryKey';
|
||||
|
||||
type AggregateVersion<T> = {
|
||||
_id: string
|
||||
version: T
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
type Args = {
|
||||
accessResult: AccessResult
|
||||
collection: Collection
|
||||
locale: string
|
||||
paginationOptions: any
|
||||
payload: Payload
|
||||
where: Where
|
||||
}
|
||||
|
||||
export const queryDrafts = async <T extends TypeWithID>({
|
||||
accessResult,
|
||||
collection,
|
||||
locale,
|
||||
payload,
|
||||
paginationOptions,
|
||||
where: incomingWhere,
|
||||
}: Args): Promise<PaginatedDocs<T>> => {
|
||||
const VersionModel = payload.versions[collection.config.slug] as CollectionModel;
|
||||
|
||||
const where = appendVersionToQueryKey(incomingWhere || {});
|
||||
|
||||
const versionQueryToBuild: { where: Where } = {
|
||||
where: {
|
||||
...where,
|
||||
and: [
|
||||
...where?.and || [],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
if (hasWhereAccessResult(accessResult)) {
|
||||
const versionAccessResult = appendVersionToQueryKey(accessResult);
|
||||
versionQueryToBuild.where.and.push(versionAccessResult);
|
||||
}
|
||||
|
||||
const versionQuery = await VersionModel.buildQuery(versionQueryToBuild, locale);
|
||||
|
||||
const aggregate = VersionModel.aggregate<AggregateVersion<T>>([
|
||||
// Sort so that newest are first
|
||||
{ $sort: { updatedAt: -1 } },
|
||||
// Group by parent ID, and take the first of each
|
||||
{
|
||||
$group: {
|
||||
_id: '$parent',
|
||||
version: { $first: '$version' },
|
||||
updatedAt: { $first: '$updatedAt' },
|
||||
createdAt: { $first: '$createdAt' },
|
||||
},
|
||||
},
|
||||
// Filter based on incoming query
|
||||
{ $match: versionQuery },
|
||||
// Re-sort based on incoming sort
|
||||
{
|
||||
$sort: Object.entries(paginationOptions.sort).reduce((sort, [key, order]) => {
|
||||
return {
|
||||
...sort,
|
||||
[key]: order === 'asc' ? 1 : -1,
|
||||
};
|
||||
}, {}),
|
||||
},
|
||||
// Add pagination limit
|
||||
{ $limit: paginationOptions.limit },
|
||||
]);
|
||||
|
||||
const result = await VersionModel.aggregatePaginate(aggregate, paginationOptions);
|
||||
|
||||
return {
|
||||
...result,
|
||||
docs: result.docs.map((doc) => ({
|
||||
_id: doc._id,
|
||||
...doc.version,
|
||||
updatedAt: doc.updatedAt,
|
||||
createdAt: doc.createdAt,
|
||||
})),
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Payload } from '../..';
|
||||
import { Payload } from '../../payload';
|
||||
import { docHasTimestamps, Where } from '../../types';
|
||||
import { hasWhereAccessResult } from '../../auth';
|
||||
import { AccessResult } from '../../config/types';
|
||||
import { CollectionModel, SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types';
|
||||
import flattenWhereConstraints from '../../utilities/flattenWhereConstraints';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { appendVersionToQueryKey } from './appendVersionToQueryKey';
|
||||
import { SanitizedGlobalConfig } from '../../globals/config/types';
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import { Payload } from '../..';
|
||||
import { SanitizedCollectionConfig } from '../../collections/config/types';
|
||||
import { enforceMaxVersions } from '../enforceMaxVersions';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
|
||||
type Args = {
|
||||
payload: Payload
|
||||
config?: SanitizedCollectionConfig
|
||||
req: PayloadRequest
|
||||
data: any
|
||||
id: string | number
|
||||
autosave: boolean
|
||||
}
|
||||
|
||||
export const saveCollectionDraft = async ({
|
||||
payload,
|
||||
config,
|
||||
id,
|
||||
data,
|
||||
autosave,
|
||||
}: Args): Promise<Record<string, unknown>> => {
|
||||
const VersionsModel = payload.versions[config.slug];
|
||||
|
||||
const dataAsDraft = { ...data, _status: 'draft' };
|
||||
|
||||
let existingAutosaveVersion;
|
||||
|
||||
if (autosave) {
|
||||
existingAutosaveVersion = await VersionsModel.findOne({
|
||||
parent: id,
|
||||
}, {}, { sort: { updatedAt: 'desc' } });
|
||||
}
|
||||
|
||||
let result;
|
||||
|
||||
try {
|
||||
// If there is an existing autosave document,
|
||||
// Update it
|
||||
if (autosave && existingAutosaveVersion?.autosave === true) {
|
||||
result = await VersionsModel.findByIdAndUpdate(
|
||||
{
|
||||
_id: existingAutosaveVersion._id,
|
||||
},
|
||||
{
|
||||
version: dataAsDraft,
|
||||
},
|
||||
{ new: true, lean: true },
|
||||
);
|
||||
// Otherwise, create a new one
|
||||
} else {
|
||||
result = await VersionsModel.create({
|
||||
parent: id,
|
||||
version: dataAsDraft,
|
||||
autosave: Boolean(autosave),
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
payload.logger.error(`There was an error while creating a draft ${config.labels.singular} with ID ${id}.`);
|
||||
payload.logger.error(err);
|
||||
}
|
||||
|
||||
if (config.versions.maxPerDoc) {
|
||||
enforceMaxVersions({
|
||||
id,
|
||||
payload,
|
||||
Model: VersionsModel,
|
||||
slug: config.slug,
|
||||
entityType: 'collection',
|
||||
max: config.versions.maxPerDoc,
|
||||
});
|
||||
}
|
||||
|
||||
result = result.version;
|
||||
result = JSON.stringify(result);
|
||||
result = JSON.parse(result);
|
||||
result.id = id;
|
||||
|
||||
return result;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user