feat: mongoose-adapter: findOne, sort direction, id => where and cleanup (#2918)

* chore: add jsDocs for buildQuery

* feat: where instead of id for updateOne and deleteOne

* feat: find => findOne

* sort order => sort direction

* fix: typing of Global buildQuery

* cleanup

* fix: init payload's i18n for error message

* fix: incorrect use of FindArgs in findByID

* move deleteOne call to adapter

* re-order

* deleteVersions

* versions stuff

* more version stuff

* moar version stuff

* global stuff

* global stuff

* move combineQueries inside the findGlobal

* global stuff

* fix type

* more global stuff

* move docWithFilenameExists to adapter pattern

* chore: remove unnecessary comments

* perf: make everything lean, disable virtuals, ++performance

* chore: remove unnecessary Model
This commit is contained in:
Alessio Gravili
2023-06-28 16:19:43 +02:00
committed by GitHub
parent bc5aeb7840
commit b4c049c745
51 changed files with 539 additions and 367 deletions

View File

@@ -42,14 +42,11 @@ const getExecuteStaticAccess = (config: SanitizedCollectionConfig) => async (req
});
}
const { docs } = await req.payload.db.find({
const doc = await req.payload.db.findOne({
collection: config.slug,
where: queryToBuild,
limit: 1,
});
const doc = docs[0];
if (!doc) {
throw new Forbidden(req.t);
}

View File

@@ -68,13 +68,11 @@ async function forgotPassword(incomingArgs: Arguments): Promise<string | null> {
resetPasswordExpiration?: Date,
}
const { docs } = await payload.db.find<UserDoc>({
let user = await payload.db.findOne<UserDoc>({
collection: collectionConfig.slug,
where: { email: { equals: (data.email as string).toLowerCase() } },
limit: 1,
});
let [user] = docs;
if (!user) return null;

View File

@@ -6,13 +6,11 @@ async function init(args: { req: PayloadRequest, collection: string }): Promise<
collection: slug,
} = args;
const { docs } = await payload.db.find({
const doc = await payload.db.findOne({
collection: slug,
limit: 1,
pagination: false,
});
return docs.length === 1;
return !!doc;
}
export default init;

View File

@@ -77,16 +77,11 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
const email = unsanitizedEmail ? (unsanitizedEmail as string).toLowerCase().trim() : null;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Improper typing in library, additional args should be optional
const { docs } = await payload.db.find<any>({
let user = await payload.db.findOne<any>({
collection: collectionConfig.slug,
where: { email: { equals: email.toLowerCase() } },
limit: 1,
});
let [user] = docs;
if (!user || (args.collection.config.auth.verify && user._verified === false)) {
throw new AuthenticationError(req.t);
}

View File

@@ -40,13 +40,11 @@ async function registerFirstUser<TSlug extends keyof GeneratedTypes['collections
data,
} = args;
const { docs } = await payload.db.find({
const doc = await payload.db.findOne({
collection: config.slug,
limit: 1,
pagination: false,
});
if (docs.length === 1) throw new Forbidden(req.t);
if (doc) throw new Forbidden(req.t);
// /////////////////////////////////////
// Register first user

View File

@@ -50,17 +50,14 @@ async function resetPassword(args: Arguments): Promise<Result> {
// Reset Password
// /////////////////////////////////////
const { docs } = await payload.db.find<any>({
const user = await payload.db.findOne<any>({
collection: collectionConfig.slug,
limit: 1,
where: {
resetPasswordToken: { equals: data.token },
resetPasswordExpiration: { greater_than: Date.now() },
},
});
const [user] = docs;
if (!user) throw new APIError('Token is either invalid or has expired.');
// TODO: replace this method
@@ -77,7 +74,7 @@ async function resetPassword(args: Arguments): Promise<Result> {
const doc = await payload.db.updateOne({
collection: collectionConfig.slug,
id: user.id,
where: { id: { equals: user.id } },
data: user,
});

View File

@@ -45,15 +45,12 @@ async function unlock(args: Args): Promise<boolean> {
// Unlock
// /////////////////////////////////////
const { docs } = await req.payload.db.find({
const user = await req.payload.db.findOne({
collection: collectionConfig.slug,
where: { email: { equals: data.email.toLowerCase() } },
limit: 1,
locale,
});
const [user] = docs;
if (!user) return null;
await resetLoginAttempts({

View File

@@ -19,22 +19,19 @@ async function verifyEmail(args: Args): Promise<boolean> {
throw new APIError('Missing required data.', httpStatus.BAD_REQUEST);
}
const { docs } = await req.payload.db.find<any>({
const user = await req.payload.db.findOne<any>({
collection: collection.config.slug,
limit: 1,
where: {
_verificationToken: { equals: token },
},
});
const [user] = docs;
if (!user) throw new APIError('Verification token is invalid.', httpStatus.BAD_REQUEST);
if (user && user._verified === true) throw new APIError('This account has already been activated.', httpStatus.ACCEPTED);
await req.payload.db.updateOne({
collection: collection.config.slug,
id: user.id,
where: { id: { equals: user.id } },
data: {
_verified: true,
_verificationToken: undefined,

View File

@@ -4,7 +4,7 @@ import { AggregatePaginateModel, IndexDefinition, IndexOptions, Model, PaginateM
import { GraphQLInputObjectType, GraphQLNonNull, GraphQLObjectType } from 'graphql';
import { Response } from 'express';
import { Config as GeneratedTypes } from 'payload/generated-types';
import { Where } from '../../types';
import type { Where } from '../../types';
import { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types';
import { Field } from '../../fields/config/types';
import { PayloadRequest } from '../../express/types';
@@ -29,6 +29,7 @@ interface PassportLocalModel {
}
export interface CollectionModel extends Model<any>, PaginateModel<any>, AggregatePaginateModel<any>, PassportLocalModel {
/** buildQuery is used to transform payload's where operator into what can be used by mongoose (e.g. id => _id) */
buildQuery: (args: BuildQueryArgs) => Promise<Record<string, unknown>> // TODO: Delete this
}

View File

@@ -49,7 +49,6 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
const {
depth,
collection: {
Model,
config: collectionConfig,
},
where,
@@ -133,7 +132,14 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
// Delete document
// /////////////////////////////////////
await Model.deleteOne({ _id: id }, { lean: true });
await payload.db.deleteOne({
collection: collectionConfig.slug,
where: {
id: {
equals: id,
},
},
});
// /////////////////////////////////////
// Delete versions

View File

@@ -78,14 +78,12 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(inc
// Retrieve document
// /////////////////////////////////////
const { docs } = await req.payload.db.find({
const docToDelete = await req.payload.db.findOne({
collection: collectionConfig.slug,
where: combineQueries({ id: { equals: id } }, accessResults),
locale: req.locale,
limit: 1,
});
const [docToDelete] = docs;
if (!docToDelete && !hasWhereAccess) throw new NotFound(t);
if (!docToDelete && hasWhereAccess) throw new Forbidden(t);
@@ -100,7 +98,7 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(inc
let result = await req.payload.db.deleteOne({
collection: collectionConfig.slug,
id,
where: { id: { equals: id } },
});

View File

@@ -2,13 +2,12 @@
import memoize from 'micro-memoize';
import { PayloadRequest } from '../../express/types';
import { Collection, TypeWithID } from '../config/types';
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
import { NotFound } from '../../errors';
import executeAccess from '../../auth/executeAccess';
import replaceWithDraftIfAvailable from '../../versions/drafts/replaceWithDraftIfAvailable';
import { afterRead } from '../../fields/hooks/afterRead';
import { combineQueries } from '../../database/combineQueries';
import { FindArgs } from '../../database/types';
import type { FindOneArgs } from '../../database/types';
export type Arguments = {
collection: Collection
@@ -68,23 +67,22 @@ async function findByID<T extends TypeWithID>(
if (accessResult === false) return null;
const findArgs: FindArgs = {
const findOneArgs: FindOneArgs = {
collection: collectionConfig.slug,
where: combineQueries({ id: { equals: id } }, accessResult),
locale,
limit: 1,
};
// /////////////////////////////////////
// Find by ID
// /////////////////////////////////////
if (!findArgs.where.and[0].id) throw new NotFound(t);
if (!findOneArgs.where.and[0].id) throw new NotFound(t);
if (!req.findByID) req.findByID = {};
if (!req.findByID[collectionConfig.slug]) {
const nonMemoizedFindByID = async (query: FindArgs) => (await req.payload.db.find(query)).docs[0];
const nonMemoizedFindByID = async (query: FindOneArgs) => req.payload.db.findOne(query);
req.findByID[collectionConfig.slug] = memoize(nonMemoizedFindByID, {
isPromise: true,
@@ -95,7 +93,7 @@ async function findByID<T extends TypeWithID>(
});
}
let result = await req.findByID[collectionConfig.slug](findArgs) as T;
let result = await req.findByID[collectionConfig.slug](findOneArgs) as T;
if (!result) {
if (!disableErrors) {
@@ -108,7 +106,6 @@ async function findByID<T extends TypeWithID>(
// Clone the result - it may have come back memoized
result = JSON.parse(JSON.stringify(result));
result = sanitizeInternalFields(result);
// /////////////////////////////////////
// Replace document with draft if available
@@ -134,7 +131,7 @@ async function findByID<T extends TypeWithID>(
result = await hook({
req,
query: findArgs.where,
query: findOneArgs.where,
doc: result,
}) || result;
}, Promise.resolve());
@@ -162,7 +159,7 @@ async function findByID<T extends TypeWithID>(
result = await hook({
req,
query: findArgs.where,
query: findOneArgs.where,
doc: result,
}) || result;
}, Promise.resolve());

View File

@@ -9,7 +9,7 @@ import { afterChange } from '../../fields/hooks/afterChange';
import { afterRead } from '../../fields/hooks/afterRead';
import { getLatestCollectionVersion } from '../../versions/getLatestCollectionVersion';
import { combineQueries } from '../../database/combineQueries';
import { FindArgs } from '../../database/types';
import type { FindOneArgs } from '../../database/types';
export type Arguments = {
collection: Collection
@@ -47,9 +47,6 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
// Retrieve original raw version
// /////////////////////////////////////
const VersionModel = payload.versions[collectionConfig.slug];
const { docs: versionDocs } = await req.payload.db.findVersions({
collection: collectionConfig.slug,
where: { id: { equals: id } },
@@ -76,16 +73,14 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
// Retrieve document
// /////////////////////////////////////
const findArgs: FindArgs = {
const findOneArgs: FindOneArgs = {
collection: collectionConfig.slug,
where: combineQueries({ id: { equals: parentDocID } }, accessResults),
locale,
limit: 1,
};
const { docs } = await req.payload.db.find(findArgs);
const doc = await req.payload.db.findOne(findOneArgs);
const [doc] = docs;
if (!doc && !hasWherePolicy) throw new NotFound(t);
if (!doc && hasWherePolicy) throw new Forbidden(t);
@@ -97,7 +92,7 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
const prevDocWithLocales = await getLatestCollectionVersion({
payload,
id: parentDocID,
query: findArgs,
query: findOneArgs,
config: collectionConfig,
});
@@ -107,7 +102,7 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
let result = await req.payload.db.updateOne({
collection: collectionConfig.slug,
id: parentDocID,
where: { id: { equals: parentDocID } },
data: rawVersion.version,
});
@@ -120,9 +115,10 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
delete prevVersion.id;
await VersionModel.create({
await payload.db.createVersion({
collectionSlug: collectionConfig.slug,
parent: parentDocID,
version: rawVersion.version,
versionData: rawVersion.version,
autosave: false,
createdAt: prevVersion.createdAt,
updatedAt: new Date().toISOString(),

View File

@@ -245,7 +245,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
result = await req.payload.db.updateOne({
collection: collectionConfig.slug,
locale,
id,
where: { id: { equals: id } },
data: result,
});
}

View File

@@ -18,7 +18,7 @@ import { deleteAssociatedFiles } from '../../uploads/deleteAssociatedFiles';
import { unlinkTempFiles } from '../../uploads/unlinkTempFiles';
import { generatePasswordSaltHash } from '../../auth/strategies/local/generatePasswordSaltHash';
import { combineQueries } from '../../database/combineQueries';
import { FindArgs } from '../../database/types';
import type { FindOneArgs } from '../../database/types';
export type Arguments<T extends { [field: string | number | symbol]: unknown }> = {
collection: Collection
@@ -96,18 +96,17 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
// /////////////////////////////////////
const findArgs: FindArgs = {
const findOneArgs: FindOneArgs = {
collection: collectionConfig.slug,
where: combineQueries({ id: { equals: id } }, accessResults),
locale,
limit: 1,
};
const docWithLocales = await getLatestCollectionVersion({
payload,
config: collectionConfig,
id,
query: findArgs,
query: findOneArgs,
});
if (!docWithLocales && !hasWherePolicy) throw new NotFound(t);
@@ -233,7 +232,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
result = await req.payload.db.updateOne({
collection: collectionConfig.slug,
locale,
id,
where: { id: { equals: id } },
data: dataToUpdate,
});
}

View File

@@ -38,12 +38,12 @@ export interface DatabaseAdapter {
/**
* Open the connection to the database
*/
connect?: ({ config }: { config: SanitizedConfig }) => Promise<void>;
connect?: Connect;
/**
* Perform startup tasks required to interact with the database such as building Schema and models
*/
init?: ({ config }: { config: SanitizedConfig }) => Promise<void>;
init?: Init;
/**
* Terminate the connection with the database
@@ -53,7 +53,7 @@ export interface DatabaseAdapter {
/**
* Used to alias server only modules or make other changes to webpack configuration
*/
webpack?: (config: Configuration) => Configuration;
webpack?: Webpack;
// migrations
/**
@@ -112,23 +112,36 @@ export interface DatabaseAdapter {
*/
commitTransaction?: () => Promise<boolean>;
// versions
queryDrafts: <T = TypeWithID>(args: QueryDraftsArgs) => Promise<PaginatedDocs<T>>;
queryDrafts: QueryDrafts;
// operations
find: <T = TypeWithID>(args: FindArgs) => Promise<PaginatedDocs<T>>;
findGlobal: FindGlobal;
findVersions: <T = TypeWithID>(args: FindVersionArgs) => Promise<PaginatedDocs<TypeWithVersion<T>>>;
findGlobalVersions: <T = TypeWithID>(args: FindGlobalVersionArgs) => Promise<PaginatedDocs<TypeWithVersion<T>>>;
// operations - collections
find: Find;
findOne: FindOne;
create: Create;
updateOne: UpdateOne;
deleteOne: DeleteOne;
deleteMany: DeleteMany;
// operations - globals
findGlobal: FindGlobal;
createGlobal: CreateGlobal;
updateGlobal: UpdateGlobal;
// versions
findVersions: FindVersions;
findGlobalVersions: FindGlobalVersions;
createVersion: CreateVersion;
updateVersion: UpdateVersion;
deleteVersions: DeleteVersions;
}
export type Init = ({ config }: { config: SanitizedConfig }) => Promise<void>;
export type Connect = ({ config }: { config: SanitizedConfig }) => Promise<void>
export type Webpack = (config: Configuration) => Configuration;
export type QueryDraftsArgs = {
collection: string
where?: Where
@@ -139,6 +152,17 @@ export type QueryDraftsArgs = {
locale?: string
}
export type QueryDrafts = <T = TypeWithID>(args: QueryDraftsArgs) => Promise<PaginatedDocs<T>>;
export type FindOneArgs = {
collection: string
where?: Where
locale?: string
}
export type FindOne = <T = TypeWithID>(args: FindOneArgs) => Promise<T|null>
export type FindArgs = {
collection: string
where?: Where
@@ -152,7 +176,9 @@ export type FindArgs = {
locale?: string
}
export type FindVersionArgs = {
export type Find = <T = TypeWithID>(args: FindArgs) => Promise<PaginatedDocs<T>>;
export type FindVersionsArgs = {
collection: string
where?: Where
page?: number
@@ -164,7 +190,10 @@ export type FindVersionArgs = {
locale?: string
}
export type FindGlobalVersionArgs = {
export type FindVersions = <T = TypeWithID>(args: FindVersionsArgs) => Promise<PaginatedDocs<TypeWithVersion<T>>>;
export type FindGlobalVersionsArgs = {
global: string
where?: Where
page?: number
@@ -179,53 +208,81 @@ export type FindGlobalVersionArgs = {
export type FindGlobalArgs = {
slug: string
locale?: string
where: Where
where?: Where
}
type FindGlobal = <T extends GlobalsTypeWithID = any>(args: FindGlobalArgs) => Promise<T>
export type FindGlobal = <T extends GlobalsTypeWithID = any>(args: FindGlobalArgs) => Promise<T>
export type FindOneArgs = {
export type CreateGlobalArgs<T extends GlobalsTypeWithID = any> = {
slug: string
data: T
}
export type CreateGlobal = <T extends GlobalsTypeWithID = any>(args: CreateGlobalArgs<T>) => Promise<T>
export type UpdateGlobalArgs<T extends GlobalsTypeWithID = any> = {
slug: string
data: T
}
export type UpdateGlobal = <T extends GlobalsTypeWithID = any>(args: UpdateGlobalArgs<T>) => Promise<T>
export type FindGlobalVersions = <T = TypeWithID>(args: FindGlobalVersionsArgs) => Promise<PaginatedDocs<TypeWithVersion<T>>>;
export type DeleteVersionsArgs = {
collection: string
where: Where
locale?: string
sort?: {
[key: string]: string,
}
};
export type CreateVersionArgs<T = TypeWithID> = {
collectionSlug: string
/** ID of the parent document for which the version should be created for */
parent: string | number
versionData: T
autosave: boolean
createdAt: string
updatedAt: string
}
export type CreateVersion = <T = TypeWithID>(args: CreateVersionArgs<T>) => Promise<TypeWithVersion<T>>;
export type DeleteVersions = (args: DeleteVersionsArgs) => Promise<void>;
export type UpdateVersionArgs<T = TypeWithID> = {
collectionSlug: string,
where: Where,
locale?: string,
versionData: T
}
export type UpdateVersion = <T = TypeWithID>(args: UpdateVersionArgs<T>) => Promise<TypeWithVersion<T>>
type FindOne = (args: FindOneArgs) => Promise<PaginatedDocs>
export type CreateArgs = {
collection: string
data: Record<string, unknown>
}
type Create = (args: CreateArgs) => Promise<Document>
export type Create = (args: CreateArgs) => Promise<Document>
export type UpdateOneArgs = {
collection: string,
data: Record<string, unknown>,
id: string | number,
where: Where,
locale?: string
}
type UpdateOne = (args: UpdateOneArgs) => Promise<Document>
export type UpdateOne = (args: UpdateOneArgs) => Promise<Document>
export type DeleteOneArgs = {
collection: string,
id: string | number,
}
type DeleteOne = (args: DeleteOneArgs) => Promise<Document>
type DeleteManyArgs = {
collection: string,
where: Where,
}
type DeleteMany = (args: DeleteManyArgs) => Promise<Document>
export type DeleteOne = (args: DeleteOneArgs) => Promise<Document>
export type BuildSchema<TSchema> = (args: {
config: SanitizedConfig,
@@ -325,7 +382,7 @@ export type FieldGenerator<TSchema, TField> = {
export type SortArgs = {
property: string
order: SortOrder
direction: SortDirection
}[]
export type SortOrder = 'asc' | 'desc';
export type SortDirection = 'asc' | 'desc';

View File

@@ -5,7 +5,7 @@ import { UploadedFile } from 'express-fileupload';
import { Payload } from '../payload';
import { Collection, TypeWithID } from '../collections/config/types';
import { User } from '../auth/types';
import { FindArgs } from '../database/types';
import type { FindOneArgs } from '../database/types';
/** Express request with some Payload related context added */
export declare type PayloadRequest<U = any> = Request & {
@@ -46,6 +46,6 @@ export declare type PayloadRequest<U = any> = Request & {
payloadUploadSizes?: Record<string, Buffer>;
/** Cache of documents related to the current request */
findByID?: {
[slug: string]: (q: FindArgs) => Promise<TypeWithID>;
[slug: string]: (q: FindOneArgs) => Promise<TypeWithID>;
};
};

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { Document, Model } from 'mongoose';
import { DeepRequired } from 'ts-essentials';
import { GraphQLNonNull, GraphQLObjectType } from 'graphql';
import type { Where } from '../../types';
import { User } from '../../auth/types';
import { PayloadRequest } from '../../express/types';
import { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types';
@@ -44,12 +45,12 @@ export type BeforeReadHook = (args: {
export type AfterReadHook = (args: {
doc: any
req: PayloadRequest
query?: { [key: string]: any }
query?: Where
findMany?: boolean
}) => any;
export interface GlobalModel extends Model<Document> {
buildQuery: (query: unknown, locale?: string) => Record<string, unknown>
buildQuery: (query: unknown, locale?: string) => Promise<Record<string, unknown>>
}
export type GlobalAdminOptions = {

View File

@@ -1,10 +1,10 @@
import type { Where } from '../../types';
import executeAccess from '../../auth/executeAccess';
import { AccessResult } from '../../config/types';
import replaceWithDraftIfAvailable from '../../versions/drafts/replaceWithDraftIfAvailable';
import { afterRead } from '../../fields/hooks/afterRead';
import { SanitizedGlobalConfig } from '../config/types';
import { PayloadRequest } from '../../express/types';
import { combineQueries } from '../../database/combineQueries';
type Args = {
globalConfig: SanitizedGlobalConfig
@@ -48,8 +48,11 @@ async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T
let doc = await req.payload.db.findGlobal({
slug,
locale,
where: overrideAccess ? { globalType: { equals: slug } } : combineQueries({ globalType: { equals: slug } }, accessResult),
where: overrideAccess ? undefined : accessResult as Where,
});
if (!doc) {
doc = {};
}
// /////////////////////////////////////
// Replace document with draft if available

View File

@@ -1,12 +1,12 @@
/* eslint-disable no-underscore-dangle */
import { PayloadRequest } from '../../express/types';
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
import { Forbidden, NotFound } from '../../errors';
import executeAccess from '../../auth/executeAccess';
import { TypeWithVersion } from '../../versions/types';
import { SanitizedGlobalConfig } from '../config/types';
import { afterRead } from '../../fields/hooks/afterRead';
import { combineQueries } from '../../database/combineQueries';
import { FindGlobalVersionsArgs } from '../../database/types';
export type Arguments = {
globalConfig: SanitizedGlobalConfig
@@ -36,8 +36,6 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
showHiddenFields,
} = args;
const VersionsModel = payload.versions[globalConfig.slug];
// /////////////////////////////////////
// Access
// /////////////////////////////////////
@@ -49,22 +47,22 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
const hasWhereAccess = typeof accessResults === 'object';
const query = await VersionsModel.buildQuery({
where: combineQueries({ _id: { equals: id } }, accessResults),
payload,
const findGlobalVersionsArgs: FindGlobalVersionsArgs = {
global: globalConfig.slug,
where: combineQueries({ id: { equals: id } }, accessResults),
locale,
globalSlug: globalConfig.slug,
});
limit: 1,
};
// /////////////////////////////////////
// Find by ID
// /////////////////////////////////////
if (!query.$and[0]._id) throw new NotFound(t);
if (!findGlobalVersionsArgs.where.and[0].id) throw new NotFound(t);
let result = await VersionsModel.findOne(query, {}).lean();
if (!result) {
const { docs: results } = await payload.db.findGlobalVersions(findGlobalVersionsArgs);
if (!results || results?.length === 0) {
if (!disableErrors) {
if (!hasWhereAccess) throw new NotFound(t);
if (hasWhereAccess) throw new Forbidden(t);
@@ -73,10 +71,9 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
return null;
}
// Clone the result - it may have come back memoized
result = JSON.parse(JSON.stringify(result));
result = sanitizeInternalFields(result);
// Clone the result - it may have come back memoized
let result = JSON.parse(JSON.stringify(results[0]));
// /////////////////////////////////////
// beforeRead - Collection
@@ -85,7 +82,7 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
await globalConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
await priorHook;
result.version = await hook({
result = await hook({
req,
doc: result.version,
}) || result.version;
@@ -114,7 +111,7 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
result.version = await hook({
req,
query,
query: findGlobalVersionsArgs.where,
doc: result.version,
}) || result.version;
}, Promise.resolve());

View File

@@ -56,7 +56,7 @@ export default async function updateLocal<TSlug extends keyof GeneratedTypes['gl
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);
return update<TSlug>({
slug: globalSlug,
slug: globalSlug as string,
data,
depth,
globalConfig,

View File

@@ -1,6 +1,5 @@
import { PayloadRequest } from '../../express/types';
import executeAccess from '../../auth/executeAccess';
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
import { TypeWithVersion } from '../../versions/types';
import { SanitizedGlobalConfig } from '../config/types';
import { NotFound } from '../../errors';
@@ -25,11 +24,6 @@ async function restoreVersion<T extends TypeWithVersion<T> = any>(args: Argument
req: {
t,
payload,
payload: {
globals: {
Model,
},
},
},
overrideAccess,
showHiddenFields,
@@ -47,17 +41,18 @@ async function restoreVersion<T extends TypeWithVersion<T> = any>(args: Argument
// Retrieve original raw version
// /////////////////////////////////////
const VersionModel = payload.versions[globalConfig.slug];
let rawVersion = await VersionModel.findOne({
_id: id,
const { docs: versionDocs } = await payload.db.findGlobalVersions<any>({
global: globalConfig.slug,
where: { id: { equals: id } },
limit: 1,
});
if (!rawVersion) {
if (!versionDocs || versionDocs.length === 0) {
throw new NotFound(t);
}
rawVersion = rawVersion.toJSON({ virtuals: true });
const rawVersion = versionDocs[0];
// /////////////////////////////////////
// fetch previousDoc
@@ -72,29 +67,24 @@ async function restoreVersion<T extends TypeWithVersion<T> = any>(args: Argument
// Update global
// /////////////////////////////////////
const global = await Model.findOne({ globalType: globalConfig.slug });
const global = await payload.db.findGlobal({
slug: globalConfig.slug,
});
let result = rawVersion.version;
if (global) {
result = await Model.findOneAndUpdate(
{ globalType: globalConfig.slug },
result,
{ new: true },
);
result = await payload.db.updateGlobal({
slug: globalConfig.slug,
data: result,
});
} else {
result.globalType = globalConfig.slug;
result = await Model.create(result);
result = await payload.db.createGlobal({
slug: globalConfig.slug,
data: result,
});
}
result = result.toJSON({ virtuals: true });
// custom id type reset
result.id = result._id;
result = JSON.stringify(result);
result = JSON.parse(result);
result = sanitizeInternalFields(result);
// /////////////////////////////////////
// afterRead - Fields
// /////////////////////////////////////

View File

@@ -1,5 +1,6 @@
import { Config as GeneratedTypes } from 'payload/generated-types';
import { DeepPartial } from 'ts-essentials';
import type { Where } from '../../types';
import { SanitizedGlobalConfig } from '../config/types';
import executeAccess from '../../auth/executeAccess';
import { beforeChange } from '../../fields/hooks/beforeChange';
@@ -10,11 +11,10 @@ import { PayloadRequest } from '../../express/types';
import { saveVersion } from '../../versions/saveVersion';
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
import { getLatestGlobalVersion } from '../../versions/getLatestGlobalVersion';
import { combineQueries } from '../../database/combineQueries';
type Args<T extends { [field: string | number | symbol]: unknown }> = {
globalConfig: SanitizedGlobalConfig
slug: string | number | symbol
slug: string
req: PayloadRequest
depth?: number
overrideAccess?: boolean
@@ -34,11 +34,6 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
req: {
payload,
locale,
payload: {
globals: {
Model,
},
},
},
depth,
overrideAccess,
@@ -61,24 +56,17 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
// Retrieve document
// /////////////////////////////////////
const query = await Model.buildQuery({
where: combineQueries({ globalType: { equals: slug } }, accessResults),
payload,
locale,
overrideAccess,
globalSlug: slug,
});
const query: Where = overrideAccess ? undefined : accessResults as Where;
// /////////////////////////////////////
// 2. Retrieve document
// /////////////////////////////////////
const { global, globalExists } = await getLatestGlobalVersion({
payload,
Model,
config: globalConfig,
query,
lean: true,
slug,
where: query,
locale,
});
let globalJSON: Record<string, unknown> = {};
@@ -161,20 +149,18 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
if (!shouldSaveDraft) {
if (globalExists) {
result = await Model.findOneAndUpdate(
{ globalType: slug },
result,
{ new: true },
);
result = await payload.db.updateGlobal({
slug,
data: result,
});
} else {
result.globalType = slug;
result = await Model.create(result);
result = await payload.db.createGlobal({
slug,
data: result,
});
}
}
result = JSON.parse(JSON.stringify(result));
result = sanitizeInternalFields(result);
// /////////////////////////////////////
// Create version
// /////////////////////////////////////

View File

@@ -2,13 +2,11 @@
import type { ConnectOptions } from 'mongoose';
import mongoose from 'mongoose';
import { SanitizedConfig } from 'payload/config';
import type { MongooseAdapter } from '.';
import type { Connect } from '../database/types';
export async function connect(
this: MongooseAdapter,
{ config }: { config: SanitizedConfig },
): Promise<void> {
export const connect: Connect = async function connect(this: MongooseAdapter,
{ config }) {
let urlToConnect = this.url;
let successfulConnectionMessage = 'Connected to MongoDB server successfully!';
@@ -61,4 +59,4 @@ export async function connect(
this.mongoMemoryServer = mongoMemoryServer;
return mongoMemoryServer;
}
};

View File

@@ -1,23 +1,21 @@
import type { MongooseAdapter } from '.';
import { CreateArgs } from '../database/types';
import type { Create } from '../database/types';
import { Document } from '../types';
export async function create<T = unknown>(
this: MongooseAdapter,
{ collection, data }: CreateArgs,
): Promise<T> {
export const create: Create = async function create(this: MongooseAdapter,
{ collection, data }) {
const Model = this.collections[collection];
const doc = await Model.create(data);
let result: Document = doc.toJSON({ virtuals: true });
// doc.toJSON does not do stuff like converting ObjectIds to string, or date strings to date objects. That's why we use JSON.parse/stringify here
const result: Document = JSON.parse(JSON.stringify(doc));
const verificationToken = doc._verificationToken;
// custom id type reset
result.id = result._id;
result = JSON.parse(JSON.stringify(result));
if (verificationToken) {
result._verificationToken = verificationToken;
}
return result;
}
};

View File

@@ -0,0 +1,22 @@
import type { MongooseAdapter } from '.';
import type { CreateGlobal } from '../database/types';
import sanitizeInternalFields from '../utilities/sanitizeInternalFields';
export const createGlobal: CreateGlobal = async function createGlobal(this: MongooseAdapter,
{ data, slug }) {
const Model = this.globals;
let result = await Model.create({
globalType: slug,
...data,
}) as any;
result = JSON.parse(JSON.stringify(result));
// custom id type reset
result.id = result._id;
result = sanitizeInternalFields(result);
return result;
};

View File

@@ -0,0 +1,27 @@
import type { MongooseAdapter } from '.';
import type { CreateVersion } from '../database/types';
import type { Document } from '../types';
export const createVersion: CreateVersion = async function createVersion(this: MongooseAdapter,
{ collectionSlug, parent, versionData, autosave, createdAt, updatedAt }) {
const VersionModel = this.versions[collectionSlug];
const doc = await VersionModel.create({
parent,
version: versionData,
autosave,
createdAt,
updatedAt,
});
const result: Document = JSON.parse(JSON.stringify(doc));
const verificationToken = doc._verificationToken;
// custom id type reset
result.id = result._id;
if (verificationToken) {
result._verificationToken = verificationToken;
}
return result;
};

View File

@@ -1,24 +1,26 @@
import type { MongooseAdapter } from '.';
import type { DeleteOneArgs } from '../database/types';
import type { DeleteOne } from '../database/types';
import type { Document } from '../types';
import sanitizeInternalFields from '../utilities/sanitizeInternalFields';
import { Document } from '../types';
export async function deleteOne<T = unknown>(
this: MongooseAdapter,
{ collection, id }: DeleteOneArgs,
): Promise<T> {
export const deleteOne: DeleteOne = async function deleteOne(this: MongooseAdapter,
{ collection, where }) {
const Model = this.collections[collection];
const doc = await Model.findOneAndDelete({ _id: id });
const query = await Model.buildQuery({
payload: this.payload,
where,
});
let result: Document = doc.toJSON({ virtuals: true });
const doc = await Model.findOneAndDelete(query).lean();
let result: Document = JSON.parse(JSON.stringify(doc));
// custom id type reset
result.id = result._id;
result = JSON.stringify(result);
result = JSON.parse(result);
result = sanitizeInternalFields(result);
return result;
}
};

View File

@@ -0,0 +1,16 @@
import type { MongooseAdapter } from '.';
import type { DeleteVersions } from '../database/types';
export const deleteVersions: DeleteVersions = async function deleteVersions(this: MongooseAdapter,
{ collection, where, locale }) {
const VersionsModel = this.versions[collection];
const query = await VersionsModel.buildQuery({
payload: this.payload,
locale,
where,
});
await VersionsModel.deleteMany(query).lean();
};

View File

@@ -1,14 +1,12 @@
import type { PaginateOptions } from 'mongoose';
import type { MongooseAdapter } from '.';
import { PaginatedDocs } from './types';
import { FindArgs } from '../database/types';
import type { Find } from '../database/types';
import sanitizeInternalFields from '../utilities/sanitizeInternalFields';
import flattenWhereToOperators from '../database/flattenWhereToOperators';
export async function find<T = unknown>(
this: MongooseAdapter,
{ collection, where, page, limit, sort, locale, pagination }: FindArgs,
): Promise<PaginatedDocs<T>> {
export const find: Find = async function find(this: MongooseAdapter,
{ collection, where, page, limit, sort, locale, pagination }) {
const Model = this.collections[collection];
let useEstimatedCount = false;
@@ -27,7 +25,7 @@ export async function find<T = unknown>(
const paginationOptions: PaginateOptions = {
page,
sort: sort ? sort.reduce((acc, cur) => {
acc[cur.property] = cur.order;
acc[cur.property] = cur.direction;
return acc;
}, {}) : undefined,
limit,
@@ -42,13 +40,14 @@ export async function find<T = unknown>(
};
const result = await Model.paginate(query, paginationOptions);
const docs = JSON.parse(JSON.stringify(result.docs));
return {
...result,
docs: result.docs.map((doc) => {
const sanitizedDoc = JSON.parse(JSON.stringify(doc));
sanitizedDoc.id = sanitizedDoc._id;
return sanitizeInternalFields(sanitizedDoc);
docs: docs.map((doc) => {
// eslint-disable-next-line no-param-reassign
doc.id = doc._id;
return sanitizeInternalFields(doc);
}),
};
}
};

View File

@@ -1,16 +1,14 @@
import type { MongooseAdapter } from '.';
import { FindGlobalArgs } from '../database/types';
import { combineQueries } from '../database/combineQueries';
import type { FindGlobal } from '../database/types';
import sanitizeInternalFields from '../utilities/sanitizeInternalFields';
import { TypeWithID } from '../globals/config/types';
export async function findGlobal<T extends TypeWithID = any>(
this: MongooseAdapter,
{ slug, locale, where }: FindGlobalArgs,
): Promise<T> {
export const findGlobal: FindGlobal = async function findGlobal(this: MongooseAdapter,
{ slug, locale, where }) {
const Model = this.globals;
const query = await Model.buildQuery({
where,
where: combineQueries({ globalType: { equals: slug } }, where),
payload: this.payload,
locale,
globalSlug: slug,
@@ -19,8 +17,8 @@ export async function findGlobal<T extends TypeWithID = any>(
let doc = await Model.findOne(query).lean() as any;
if (!doc) {
doc = {};
} else if (doc._id) {
return null;
} if (doc._id) {
doc.id = doc._id;
delete doc._id;
}
@@ -30,4 +28,4 @@ export async function findGlobal<T extends TypeWithID = any>(
return doc;
}
};

View File

@@ -1,14 +1,10 @@
import type { MongooseAdapter } from '.';
import { PaginatedDocs } from './types';
import { FindGlobalVersionArgs } from '../database/types';
import type { FindGlobalVersions } from '../database/types';
import sanitizeInternalFields from '../utilities/sanitizeInternalFields';
import flattenWhereToOperators from '../database/flattenWhereToOperators';
import type { TypeWithVersion } from '../versions/types';
export async function findGlobalVersions<T = unknown>(
this: MongooseAdapter,
{ global, where, page, limit, sort, locale, pagination, skip }: FindGlobalVersionArgs,
): Promise<PaginatedDocs<TypeWithVersion<T>>> {
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(this: MongooseAdapter,
{ global, where, page, limit, sort, locale, pagination, skip }) {
const Model = this.versions[global];
let useEstimatedCount = false;
@@ -28,7 +24,7 @@ export async function findGlobalVersions<T = unknown>(
const paginationOptions = {
page,
sort: sort ? sort.reduce((acc, cur) => {
acc[cur.property] = cur.order;
acc[cur.property] = cur.direction;
return acc;
}, {}) : undefined,
limit,
@@ -45,13 +41,14 @@ export async function findGlobalVersions<T = unknown>(
};
const result = await Model.paginate(query, paginationOptions);
const docs = JSON.parse(JSON.stringify(result.docs));
return {
...result,
docs: result.docs.map((doc) => {
const sanitizedDoc = JSON.parse(JSON.stringify(doc));
sanitizedDoc.id = sanitizedDoc._id;
return sanitizeInternalFields(sanitizedDoc);
docs: docs.map((doc) => {
// eslint-disable-next-line no-param-reassign
doc.id = doc._id;
return sanitizeInternalFields(doc);
}),
};
}
};

31
src/mongoose/findOne.ts Normal file
View File

@@ -0,0 +1,31 @@
import type { MongooseAdapter } from '.';
import type { FindOne } from '../database/types';
import type { Document } from '../types';
import sanitizeInternalFields from '../utilities/sanitizeInternalFields';
export const findOne: FindOne = async function findOne(this: MongooseAdapter,
{ collection, where, locale }) {
const Model = this.collections[collection];
const query = await Model.buildQuery({
payload: this.payload,
locale,
where,
});
const doc = await Model.findOne(query).lean();
if (!doc) {
return null;
}
let result: Document = JSON.parse(JSON.stringify(doc));
// custom id type reset
result.id = result._id;
result = sanitizeInternalFields(result);
return result;
};

View File

@@ -1,14 +1,10 @@
import type { MongooseAdapter } from '.';
import { PaginatedDocs } from './types';
import { FindVersionArgs } from '../database/types';
import type { FindVersions } from '../database/types';
import sanitizeInternalFields from '../utilities/sanitizeInternalFields';
import flattenWhereToOperators from '../database/flattenWhereToOperators';
import type { TypeWithVersion } from '../versions/types';
export async function findVersions<T = unknown>(
this: MongooseAdapter,
{ collection, where, page, limit, sort, locale, pagination, skip }: FindVersionArgs,
): Promise<PaginatedDocs<TypeWithVersion<T>>> {
export const findVersions: FindVersions = async function findVersions(this: MongooseAdapter,
{ collection, where, page, limit, sort, locale, pagination, skip }) {
const Model = this.versions[collection];
let useEstimatedCount = false;
@@ -27,7 +23,7 @@ export async function findVersions<T = unknown>(
const paginationOptions = {
page,
sort: sort ? sort.reduce((acc, cur) => {
acc[cur.property] = cur.order;
acc[cur.property] = cur.direction;
return acc;
}, {}) : undefined,
limit,
@@ -44,13 +40,14 @@ export async function findVersions<T = unknown>(
};
const result = await Model.paginate(query, paginationOptions);
const docs = JSON.parse(JSON.stringify(result.docs));
return {
...result,
docs: result.docs.map((doc) => {
const sanitizedDoc = JSON.parse(JSON.stringify(doc));
sanitizedDoc.id = sanitizedDoc._id;
return sanitizeInternalFields(sanitizedDoc);
docs: docs.map((doc) => {
// eslint-disable-next-line no-param-reassign
doc.id = doc._id;
return sanitizeInternalFields(doc);
}),
};
}
};

View File

@@ -1,5 +1,6 @@
import type { ConnectOptions } from 'mongoose';
import type { DatabaseAdapter } from '../database/types';
import type { Payload } from '../index';
import { connect } from './connect';
import { init } from './init';
import { webpack } from './webpack';
@@ -10,10 +11,15 @@ import { find } from './find';
import { create } from './create';
import { updateOne } from './updateOne';
import { deleteOne } from './deleteOne';
import { findGlobal } from './findGlobal';
import { findOne } from './findOne';
import { findVersions } from './findVersions';
import { findGlobalVersions } from './findGlobalVersions';
import type { Payload } from '../index';
import { findGlobal } from './findGlobal';
import { deleteVersions } from './deleteVersions';
import { createVersion } from './createVersion';
import { updateVersion } from './updateVersion';
import { updateGlobal } from './updateGlobal';
import { createGlobal } from './createGlobal';
export interface Args {
payload: Payload,
@@ -60,12 +66,18 @@ export function mongooseAdapter({ payload, url, connectOptions }: Args): Mongoos
rollbackTransaction: async () => true,
commitTransaction: async () => true,
queryDrafts,
findOne,
find,
findVersions,
findGlobal,
findGlobalVersions,
create,
updateOne,
deleteOne,
findGlobal,
createGlobal,
updateGlobal,
findVersions,
findGlobalVersions,
createVersion,
updateVersion,
deleteVersions,
};
}

View File

@@ -11,13 +11,10 @@ import { getVersionsModelName } from '../versions/getVersionsModelName';
import type { MongooseAdapter } from '.';
import { buildGlobalModel } from './models/buildGlobalModel';
import { buildVersionGlobalFields } from '../versions/buildGlobalFields';
import type { SanitizedConfig } from '../config/types';
import type { Init } from '../database/types';
export async function init(
this: MongooseAdapter,
{ config }: { config: SanitizedConfig },
): Promise<void> {
export const init: Init = async function init(this: MongooseAdapter,
{ config }) {
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const schema = buildCollectionSchema(collection, this.payload.config);
@@ -112,4 +109,4 @@ export async function init(
this.payload.versions[global.slug] = versionsModel;
}
});
}
};

View File

@@ -1,7 +1,7 @@
import { Config } from '../../config/types';
import { getLocalizedSortProperty } from './getLocalizedSortProperty';
import { Field } from '../../fields/config/types';
import type { SortArgs, SortOrder } from '../../database/types';
import type { SortArgs, SortDirection } from '../../database/types';
type Args = {
sort: string
@@ -13,7 +13,7 @@ type Args = {
export const buildSortParam = ({ sort, config, fields, timestamps, locale }: Args): SortArgs => {
let sortProperty: string;
let sortOrder: SortOrder = 'desc';
let sortDirection: SortDirection = 'desc';
if (!sort) {
if (timestamps) {
@@ -25,7 +25,7 @@ export const buildSortParam = ({ sort, config, fields, timestamps, locale }: Arg
sortProperty = sort.substring(1);
} else {
sortProperty = sort;
sortOrder = 'asc';
sortDirection = 'asc';
}
if (sortProperty === 'id') {
@@ -39,5 +39,5 @@ export const buildSortParam = ({ sort, config, fields, timestamps, locale }: Arg
});
}
return [{ property: sortProperty, order: sortOrder }];
return [{ property: sortProperty, direction: sortDirection }];
};

View File

@@ -1,6 +1,5 @@
import type { MongooseAdapter } from '.';
import { PaginatedDocs } from './types';
import { QueryDraftsArgs } from '../database/types';
import type { QueryDrafts } from '../database/types';
import flattenWhereToOperators from '../database/flattenWhereToOperators';
import sanitizeInternalFields from '../utilities/sanitizeInternalFields';
@@ -11,10 +10,8 @@ type AggregateVersion<T> = {
createdAt: string
}
export async function queryDrafts<T = unknown>(
this: MongooseAdapter,
{ collection, where, page, limit, sort, locale, pagination }: QueryDraftsArgs,
): Promise<PaginatedDocs<T>> {
export const queryDrafts: QueryDrafts = async function queryDrafts<T>(this: MongooseAdapter,
{ collection, where, page, limit, sort, locale, pagination }) {
const VersionModel = this.versions[collection];
const versionQuery = await VersionModel.buildQuery({
@@ -66,7 +63,7 @@ export async function queryDrafts<T = unknown>(
},
sort: sort ? sort.reduce((acc, cur) => {
let sanitizedSortProperty = cur.property;
const sanitizedSortOrder = cur.order === 'asc' ? 1 : -1;
const sanitizedSortOrder = cur.direction === 'asc' ? 1 : -1;
if (!['createdAt', 'updatedAt', '_id'].includes(cur.property)) {
sanitizedSortProperty = `version.${cur.property}`;
@@ -81,19 +78,21 @@ export async function queryDrafts<T = unknown>(
result = aggregate.exec();
}
const docs = JSON.parse(JSON.stringify(result.docs));
return {
...result,
docs: result.docs.map((doc) => {
let sanitizedDoc = {
docs: docs.map((doc) => {
// eslint-disable-next-line no-param-reassign
doc = {
_id: doc._id,
id: doc._id,
...doc.version,
updatedAt: doc.updatedAt,
createdAt: doc.createdAt,
};
sanitizedDoc = JSON.parse(JSON.stringify(sanitizedDoc));
sanitizedDoc.id = sanitizedDoc._id;
return sanitizeInternalFields(sanitizedDoc);
return sanitizeInternalFields(doc);
}),
};
}
};

View File

@@ -0,0 +1,24 @@
import type { MongooseAdapter } from '.';
import type { UpdateGlobal } from '../database/types';
import sanitizeInternalFields from '../utilities/sanitizeInternalFields';
export const updateGlobal: UpdateGlobal = async function updateGlobal(this: MongooseAdapter,
{ slug, data }) {
const Model = this.globals;
let result;
result = await Model.findOneAndUpdate(
{ globalType: slug },
data,
{ new: true, lean: true },
).lean();
result = JSON.parse(JSON.stringify(result));
// custom id type reset
result.id = result._id;
result = sanitizeInternalFields(result);
return result;
};

View File

@@ -1,36 +1,40 @@
import { t } from 'i18next';
import type { MongooseAdapter } from '.';
import type { UpdateOneArgs } from '../database/types';
import type { UpdateOne } from '../database/types';
import { ValidationError } from '../errors';
import sanitizeInternalFields from '../utilities/sanitizeInternalFields';
import i18nInit from '../translations/init';
export async function updateOne<T = unknown>(
this: MongooseAdapter,
{ collection, data, id, locale }: UpdateOneArgs,
): Promise<T> {
export const updateOne: UpdateOne = async function updateOne(this: MongooseAdapter,
{ collection, data, where, locale }) {
const Model = this.collections[collection];
const query = await Model.buildQuery({
payload: this.payload,
locale,
where,
});
let result;
try {
result = await Model.findByIdAndUpdate(
{ _id: id, locale },
result = await Model.findOneAndUpdate(
query,
data,
{ new: true },
);
{ new: true, lean: true },
).lean();
} catch (error) {
// 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)
}], i18nInit(this.payload.config.i18n).t)
: error;
}
result = JSON.parse(JSON.stringify(result));
result.id = result._id as string | number;
result.id = result._id;
result = sanitizeInternalFields(result);
return result;
}
};

View File

@@ -0,0 +1,31 @@
import type { MongooseAdapter } from '.';
import type { UpdateVersion } from '../database/types';
export const updateVersion: UpdateVersion = async function updateVersion(this: MongooseAdapter,
{ collectionSlug, where, locale, versionData }) {
const VersionModel = this.versions[collectionSlug];
const query = await VersionModel.buildQuery({
payload: this.payload,
locale,
where,
});
const doc = await VersionModel.findOneAndUpdate(
query,
versionData,
{ new: true, lean: true },
).lean();
const result = JSON.parse(JSON.stringify(doc));
const verificationToken = doc._verificationToken;
// custom id type reset
result.id = result._id;
if (verificationToken) {
result._verificationToken = verificationToken;
}
return result;
};

View File

@@ -1,6 +1,7 @@
import path from 'path';
import type { Webpack } from '../database/types';
export const webpack = (config) => ({
export const webpack: Webpack = (config) => ({
...config,
resolve: {
...config.resolve || {},

View File

@@ -1,7 +1,14 @@
import { CollectionModel } from '../collections/config/types';
import { Payload } from '..';
const docWithFilenameExists = async (Model: CollectionModel, path: string, filename: string): Promise<boolean> => {
const doc = await Model.findOne({ filename });
const docWithFilenameExists = async (payload: Payload, collectionSlug: string, path: string, filename: string): Promise<boolean> => {
const doc = await payload.db.findOne({
collection: collectionSlug,
where: {
filename: {
equals: filename,
},
},
});
if (doc) return true;
return false;

View File

@@ -32,7 +32,6 @@ export const generateFileData = async <T>({
config,
collection: {
config: collectionConfig,
Model,
},
req,
data,
@@ -141,7 +140,7 @@ export const generateFileData = async <T>({
fsSafeName = `${baseFilename}${ext ? `.${ext}` : ''}`;
if (!overwriteExistingFiles) {
fsSafeName = await getSafeFileName(Model, staticPath, fsSafeName);
fsSafeName = await getSafeFileName(req.payload, collectionConfig.slug, staticPath, fsSafeName);
}
fileData.filename = fsSafeName;

View File

@@ -1,7 +1,7 @@
import sanitize from 'sanitize-filename';
import { CollectionModel } from '../collections/config/types';
import docWithFilenameExists from './docWithFilenameExists';
import fileExists from './fileExists';
import { Payload } from '..';
const incrementName = (name: string) => {
const extension = name.split('.').pop();
@@ -20,11 +20,11 @@ const incrementName = (name: string) => {
return `${incrementedName}.${extension}`;
};
async function getSafeFileName(Model: CollectionModel, staticPath: string, desiredFilename: string): Promise<string> {
async function getSafeFileName(payload: Payload, collectionSlug: string, staticPath: string, desiredFilename: string): Promise<string> {
let modifiedFilename = desiredFilename;
// eslint-disable-next-line no-await-in-loop
while (await docWithFilenameExists(Model, staticPath, modifiedFilename) || await fileExists(`${staticPath}/${modifiedFilename}`)) {
while (await docWithFilenameExists(payload, collectionSlug, staticPath, modifiedFilename) || await fileExists(`${staticPath}/${modifiedFilename}`)) {
modifiedFilename = incrementName(modifiedFilename);
}
return modifiedFilename;

View File

@@ -11,12 +11,13 @@ export const deleteCollectionVersions = async ({
slug,
id,
}: Args): Promise<void> => {
const VersionsModel = payload.versions[slug];
try {
await VersionsModel.deleteMany({
parent: {
$eq: id,
await payload.db.deleteVersions({
collection: slug,
where: {
parent: {
equals: id,
},
},
});
} catch (err) {

View File

@@ -1,4 +1,3 @@
import { Payload } from '../../payload';
import { docHasTimestamps, PayloadRequest, Where } from '../../types';
import { hasWhereAccessResult } from '../../auth';
import { AccessResult } from '../../config/types';
@@ -7,7 +6,7 @@ import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
import { appendVersionToQueryKey } from './appendVersionToQueryKey';
import { SanitizedGlobalConfig } from '../../globals/config/types';
import { combineQueries } from '../../database/combineQueries';
import { FindVersionArgs } from '../../database/types';
import type { FindVersionsArgs } from '../../database/types';
type Arguments<T> = {
entity: SanitizedCollectionConfig | SanitizedGlobalConfig
@@ -58,18 +57,18 @@ const replaceWithDraftIfAvailable = async <T extends TypeWithID>({
}
const findVersionArgs: FindVersionArgs = {
const findVersionsArgs: FindVersionsArgs = {
locale: req.locale,
where: combineQueries(queryToBuild, versionAccessResult),
collection: entity.slug,
limit: 1,
sort: [{
property: 'updatedAt',
order: 'desc',
direction: 'desc',
}],
};
const { docs: versionDocs } = await req.payload.db.findVersions<T>(findVersionArgs);
const { docs: versionDocs } = await req.payload.db.findVersions<T>(findVersionsArgs);
let draft = versionDocs[0];

View File

@@ -1,12 +1,10 @@
import { FilterQuery } from 'mongoose';
import { Payload } from '../payload';
import { CollectionModel, SanitizedCollectionConfig } from '../collections/config/types';
import { Where } from '../types';
import { SanitizedGlobalConfig } from '../globals/config/types';
import type { SanitizedCollectionConfig } from '../collections/config/types';
import type { SanitizedGlobalConfig } from '../globals/config/types';
import type { Where } from '../types';
type Args = {
payload: Payload
Model: CollectionModel
max: number
collection?: SanitizedCollectionConfig
global?: SanitizedGlobalConfig
@@ -15,7 +13,6 @@ type Args = {
export const enforceMaxVersions = async ({
payload,
Model,
max,
collection,
global,
@@ -39,7 +36,7 @@ export const enforceMaxVersions = async ({
skip: max,
sort: [{
property: 'updatedAt',
order: 'desc',
direction: 'desc',
}],
pagination: false,
});
@@ -52,7 +49,7 @@ export const enforceMaxVersions = async ({
skip: max,
sort: [{
property: 'updatedAt',
order: 'desc',
direction: 'desc',
}],
});
@@ -60,15 +57,22 @@ export const enforceMaxVersions = async ({
}
if (oldestAllowedDoc?.updatedAt) {
const deleteQuery: FilterQuery<unknown> = {
const deleteQuery: Where = {
updatedAt: {
$lte: oldestAllowedDoc.updatedAt,
less_than_equal: oldestAllowedDoc.updatedAt,
},
};
if (collection) deleteQuery.parent = id;
if (collection) {
deleteQuery.parent = {
equals: id,
};
}
await Model.deleteMany(deleteQuery);
await payload.db.deleteVersions({
collection: collection?.slug,
where: deleteQuery,
});
}
} catch (err) {
payload.logger.error(`There was an error cleaning up old versions for the ${entityType} ${slug}`);

View File

@@ -2,11 +2,11 @@ import { docHasTimestamps } from '../types';
import { Payload } from '../payload';
import { SanitizedCollectionConfig, TypeWithID } from '../collections/config/types';
import { TypeWithVersion } from './types';
import { FindArgs } from '../database/types';
import type { FindOneArgs } from '../database/types';
type Args = {
payload: Payload
query: FindArgs
query: FindOneArgs
id: string | number
config: SanitizedCollectionConfig
}
@@ -25,14 +25,13 @@ export const getLatestCollectionVersion = async <T extends TypeWithID = any>({
where: { parent: { equals: id } },
sort: [{
property: 'updatedAt',
order: 'desc',
direction: 'desc',
}],
});
[latestVersion] = docs;
}
const { docs } = await payload.db.find<T>(query);
const [doc] = docs;
const doc = await payload.db.findOne<T>(query);
if (!latestVersion || (docHasTimestamps(doc) && latestVersion.updatedAt < doc.updatedAt)) {

View File

@@ -1,32 +1,39 @@
import { Payload } from '../payload';
import { docHasTimestamps, Document } from '../types';
import { GlobalModel, SanitizedGlobalConfig } from '../globals/config/types';
import { docHasTimestamps, Document, Where } from '../types';
import { SanitizedGlobalConfig } from '../globals/config/types';
type Args = {
payload: Payload
query: Record<string, unknown>
lean?: boolean
Model: GlobalModel
where: Where
slug: string
config: SanitizedGlobalConfig
locale?: string
}
export const getLatestGlobalVersion = async ({
payload,
config,
Model,
query,
lean = true,
slug,
where,
locale,
}: Args): Promise<{global: Document, globalExists: boolean}> => {
let latestVersion;
if (config.versions?.drafts) {
latestVersion = await payload.versions[config.slug].findOne({}, {}, {
sort: { updatedAt: 'desc' },
lean,
});
// eslint-disable-next-line prefer-destructuring
latestVersion = (await payload.db.findGlobalVersions({
global: slug,
limit: 1,
sort: [{ property: 'updatedAt', direction: 'desc' }],
locale,
})).docs[0];
}
const global = await (Model as any).findOne(query, {}, { lean }) as Document;
const global = await payload.db.findGlobal({
slug,
where,
locale,
});
const globalExists = Boolean(global);
if (!latestVersion || (docHasTimestamps(global) && latestVersion.updatedAt < global.updatedAt)) {

View File

@@ -1,4 +1,3 @@
import { FilterQuery } from 'mongoose';
import { Payload } from '../payload';
import { SanitizedCollectionConfig, TypeWithID } from '../collections/config/types';
import { enforceMaxVersions } from './enforceMaxVersions';
@@ -36,9 +35,6 @@ export const saveVersion = async ({
if (global) {
entityConfig = global;
}
const VersionModel = payload.versions[entityConfig.slug];
const versionData = { ...doc };
if (draft) versionData._status = 'draft';
if (versionData._id) delete versionData._id;
@@ -48,8 +44,6 @@ export const saveVersion = async ({
const now = new Date().toISOString();
if (autosave) {
const query: FilterQuery<unknown> = {};
if (collection) query.parent = id;
const { docs } = await payload.db.findVersions({
collection: entityConfig.slug,
limit: 1,
@@ -60,7 +54,7 @@ export const saveVersion = async ({
},
sort: [{
property: 'updatedAt',
order: 'desc',
direction: 'desc',
}],
});
const [latestVersion] = docs;
@@ -76,25 +70,27 @@ export const saveVersion = async ({
updatedAt: draft ? now : new Date(doc.updatedAt).toISOString(),
};
result = await VersionModel.findByIdAndUpdate(
{
_id: latestVersion.id,
result = await payload.db.updateVersion({
collectionSlug: entityConfig.slug,
versionData: data,
where: {
id: {
equals: latestVersion.id,
},
},
data,
{ new: true, lean: true },
);
});
}
}
if (createNewVersion) {
const data: Record<string, unknown> = {
result = await payload.db.createVersion({
collectionSlug: entityConfig.slug,
parent: collection ? id : undefined,
autosave: Boolean(autosave),
version: versionData,
createdAt: doc?.createdAt ? new Date(doc.createdAt).toISOString() : now,
updatedAt: draft ? now : new Date(doc.updatedAt).toISOString(),
};
if (collection) data.parent = id;
result = await VersionModel.create(data);
versionData,
});
}
} catch (err) {
let errorMessage: string;
@@ -114,7 +110,6 @@ export const saveVersion = async ({
await enforceMaxVersions({
id,
payload,
Model: VersionModel,
collection,
global,
max,