feat: move some stuff over to the new adapter pattern (#2880)

* chore: add jsdocs for limit

* moar jsdocs

* Replace find in deleteByID

* (check this:) add missing locale

* move findByID

* Make findByID return only one document

* _id => id

* _id => id

* Improve version types

* Improve sortOrder types

* move version stuff over

* version stuff

* fix: sort types

* fix params

* fix: findVersionByID generic

* fix: type error for versions

* remove unused value

* fix: Document import

* add todo

* feat: updateOne to mongoose

* remove unnecessary Model

* more update stuff

* remove unnecessary imports

* remove unnecessary function arguments

* fix: auth db update calls

* fix: bad updateByID which made tests fail

* fix: update returned docs to fix tests

* fix: update from version using mongoose directly even though the Model does not exist

* feat: implement deleteOne

* fix: assign verificationToken only when it exists - fixes test

* migrate saveVersion partly

* feat: make dev:generate-graphql-schema work even without specifying extra argument

* fix: this.db can be null

* chore: use destructured locale where possible

* chore: improve variable name

* fix: SanitizedConfig type

* feat: findGlobal database adapter

* fix: flaky e2e test

* chore: improve incorrect comment

* chore: undo diffs

* fix: id types

* fix: id typing
This commit is contained in:
Alessio Gravili
2023-06-26 17:45:52 +02:00
committed by GitHub
parent b0b30f5aef
commit 696a215dd0
29 changed files with 304 additions and 223 deletions

View File

@@ -75,15 +75,12 @@ async function resetPassword(args: Arguments): Promise<Result> {
user._verified = true;
}
const { updatedDocs } = await payload.db.update({
const doc = await payload.db.updateOne({
collection: collectionConfig.slug,
where: {
id: { equals: user.id },
},
id: user.id,
data: user,
});
const doc = sanitizeInternalFields(updatedDocs[0]);
await authenticateLocalStrategy({ password: data.password, doc });

View File

@@ -24,7 +24,7 @@ async function unlock(args: Args): Promise<boolean> {
},
req,
req: {
payload,
locale,
},
overrideAccess,
} = args;
@@ -49,6 +49,7 @@ async function unlock(args: Args): Promise<boolean> {
collection: collectionConfig.slug,
where: { email: { equals: data.email.toLowerCase() } },
limit: 1,
locale,
});
const [user] = docs;

View File

@@ -32,11 +32,9 @@ async function verifyEmail(args: Args): Promise<boolean> {
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.update({
await req.payload.db.updateOne({
collection: collection.config.slug,
where: {
id: user.id,
},
data: {
_verified: true,
_verificationToken: undefined,

View File

@@ -4,6 +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 { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types';
import { Field } from '../../fields/config/types';
import { PayloadRequest } from '../../express/types';
@@ -28,7 +29,7 @@ interface PassportLocalModel {
}
export interface CollectionModel extends Model<any>, PaginateModel<any>, AggregatePaginateModel<any>, PassportLocalModel {
buildQuery: (args: BuildQueryArgs) => Promise<Record<string, unknown>>
buildQuery: (args: BuildQueryArgs) => Promise<Record<string, unknown>> // TODO: Delete this
}
export interface AuthCollectionModel extends CollectionModel {
@@ -99,13 +100,13 @@ export type AfterChangeHook<T extends TypeWithID = any> = (args: {
export type BeforeReadHook<T extends TypeWithID = any> = (args: {
doc: T;
req: PayloadRequest;
query: { [key: string]: any };
query: Where;
}) => any;
export type AfterReadHook<T extends TypeWithID = any> = (args: {
doc: T;
req: PayloadRequest;
query?: { [key: string]: any };
query?: Where;
findMany?: boolean
}) => any;

View File

@@ -1,10 +1,11 @@
/* eslint-disable no-param-reassign */
import { Response } from 'express';
import { Collection } from '../../config/types';
import { Collection, TypeWithID } from '../../config/types';
import { PayloadRequest } from '../../../express/types';
import findVersionByID from '../../operations/findVersionByID';
import type { TypeWithVersion } from '../../../versions/types';
export type Resolver = (
export type Resolver<T extends TypeWithID = any> = (
_: unknown,
args: {
locale?: string
@@ -16,7 +17,7 @@ export type Resolver = (
req: PayloadRequest,
res: Response
}
) => Promise<Document>
) => Promise<TypeWithVersion<T>>
export default function findVersionByIDResolver(collection: Collection): Resolver {
return async function resolver(_, args, context) {

View File

@@ -1,6 +1,5 @@
import { Config as GeneratedTypes } from 'payload/generated-types';
import { PayloadRequest } from '../../express/types';
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
import { Forbidden, NotFound } from '../../errors';
import executeAccess from '../../auth/executeAccess';
import { BeforeOperationHook, Collection } from '../config/types';
@@ -40,7 +39,6 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(inc
const {
depth,
collection: {
Model,
config: collectionConfig,
},
id,
@@ -80,34 +78,31 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(inc
// Retrieve document
// /////////////////////////////////////
const query = await Model.buildQuery({
payload,
locale: req.locale,
const { docs } = await req.payload.db.find({
collection: collectionConfig.slug,
where: combineQueries({ id: { equals: id } }, accessResults),
locale: req.locale,
limit: 1,
});
const docToDelete = await Model.findOne(query);
const [docToDelete] = docs;
if (!docToDelete && !hasWhereAccess) throw new NotFound(t);
if (!docToDelete && hasWhereAccess) throw new Forbidden(t);
const resultToDelete = docToDelete.toJSON({ virtuals: true });
await deleteAssociatedFiles({ config, collectionConfig, doc: resultToDelete, t, overrideDelete: true });
await deleteAssociatedFiles({ config, collectionConfig, doc: docToDelete, t, overrideDelete: true });
// /////////////////////////////////////
// Delete document
// /////////////////////////////////////
const doc = await Model.findOneAndDelete({ _id: id });
let result: Document = doc.toJSON({ virtuals: true });
let result = await req.payload.db.deleteOne({
collection: collectionConfig.slug,
id,
});
// custom id type reset
result.id = result._id;
result = JSON.stringify(result);
result = JSON.parse(result);
result = sanitizeInternalFields(result);
// /////////////////////////////////////
// Delete Preferences

View File

@@ -8,6 +8,7 @@ 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';
export type Arguments = {
collection: Collection
@@ -42,14 +43,13 @@ async function findByID<T extends TypeWithID>(
const {
depth,
collection: {
Model,
config: collectionConfig,
},
id,
req,
req: {
t,
payload,
locale,
},
disableErrors,
currentDepth,
@@ -67,22 +67,25 @@ async function findByID<T extends TypeWithID>(
// If errors are disabled, and access returns false, return null
if (accessResult === false) return null;
const query = await Model.buildQuery({
where: combineQueries({ _id: { equals: id } }, accessResult),
payload,
locale: req.locale,
});
const findArgs: FindArgs = {
collection: collectionConfig.slug,
where: combineQueries({ id: { equals: id } }, accessResult),
locale,
limit: 1,
};
// /////////////////////////////////////
// Find by ID
// /////////////////////////////////////
if (!query.$and[0]._id) throw new NotFound(t);
if (!findArgs.where.and[0].id) throw new NotFound(t);
if (!req.findByID) req.findByID = {};
if (!req.findByID[collectionConfig.slug]) {
const nonMemoizedFindByID = async (q) => Model.findOne(q, {}).lean();
const nonMemoizedFindByID = async (query: FindArgs) => (await req.payload.db.find(query)).docs[0];
req.findByID[collectionConfig.slug] = memoize(nonMemoizedFindByID, {
isPromise: true,
maxSize: 100,
@@ -92,7 +95,7 @@ async function findByID<T extends TypeWithID>(
});
}
let result = await req.findByID[collectionConfig.slug](query);
let result = await req.findByID[collectionConfig.slug](findArgs) as T;
if (!result) {
if (!disableErrors) {
@@ -113,7 +116,6 @@ async function findByID<T extends TypeWithID>(
if (collectionConfig.versions?.drafts && draftEnabled) {
result = await replaceWithDraftIfAvailable({
payload,
entity: collectionConfig,
entityType: 'collection',
doc: result,
@@ -132,7 +134,7 @@ async function findByID<T extends TypeWithID>(
result = await hook({
req,
query,
query: findArgs.where,
doc: result,
}) || result;
}, Promise.resolve());
@@ -160,7 +162,7 @@ async function findByID<T extends TypeWithID>(
result = await hook({
req,
query,
query: findArgs.where,
doc: result,
}) || result;
}, Promise.resolve());

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-underscore-dangle */
import httpStatus from 'http-status';
import { PayloadRequest } from '../../express/types';
import { Collection } from '../config/types';
import { Collection, TypeWithID } from '../config/types';
import { APIError, Forbidden, NotFound } from '../../errors';
import executeAccess from '../../auth/executeAccess';
import { TypeWithVersion } from '../../versions/types';
@@ -19,7 +19,7 @@ export type Arguments = {
depth?: number
}
async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Arguments): Promise<T> {
async function findVersionByID<T extends TypeWithID = any>(args: Arguments): Promise<TypeWithVersion<T>> {
const {
depth,
collection: {

View File

@@ -5,11 +5,11 @@ import { Collection, TypeWithID } from '../config/types';
import { APIError, Forbidden, NotFound } from '../../errors';
import executeAccess from '../../auth/executeAccess';
import { hasWhereAccessResult } from '../../auth/types';
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
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';
export type Arguments = {
collection: Collection
@@ -25,7 +25,6 @@ export type Arguments = {
async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Promise<T> {
const {
collection: {
Model,
config: collectionConfig,
},
id,
@@ -50,16 +49,20 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
const VersionModel = payload.versions[collectionConfig.slug];
let rawVersion = await VersionModel.findOne({
_id: id,
const { docs: versionDocs } = await req.payload.db.findVersions({
collection: collectionConfig.slug,
where: { id: { equals: id } },
locale,
limit: 1,
});
const [rawVersion] = versionDocs;
if (!rawVersion) {
throw new NotFound(t);
}
rawVersion = rawVersion.toJSON({ virtuals: true });
const parentDocID = rawVersion.parent;
// /////////////////////////////////////
@@ -73,13 +76,16 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
// Retrieve document
// /////////////////////////////////////
const query = await Model.buildQuery({
const findArgs: FindArgs = {
collection: collectionConfig.slug,
where: combineQueries({ id: { equals: parentDocID } }, accessResults),
payload,
locale,
});
limit: 1,
};
const doc = await Model.findOne(query);
const { docs } = await req.payload.db.find(findArgs);
const [doc] = docs;
if (!doc && !hasWherePolicy) throw new NotFound(t);
if (!doc && hasWherePolicy) throw new Forbidden(t);
@@ -91,8 +97,7 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
const prevDocWithLocales = await getLatestCollectionVersion({
payload,
id: parentDocID,
query,
Model,
query: findArgs,
config: collectionConfig,
});
@@ -100,18 +105,12 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
// Update
// /////////////////////////////////////
let result = await Model.findByIdAndUpdate(
{ _id: parentDocID },
rawVersion.version,
{ new: true },
);
let result = await req.payload.db.updateOne({
collection: collectionConfig.slug,
id: parentDocID,
data: rawVersion.version,
result = result.toJSON({ virtuals: true });
// custom id type reset
result.id = result._id;
result = JSON.parse(JSON.stringify(result));
result = sanitizeInternalFields(result);
});
// /////////////////////////////////////
// Save `previousDoc` as a version after restoring

View File

@@ -5,7 +5,7 @@ import { Where } from '../../types';
import { BulkOperationResult, Collection } from '../config/types';
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
import executeAccess from '../../auth/executeAccess';
import { APIError, ValidationError } from '../../errors';
import { APIError } from '../../errors';
import { PayloadRequest } from '../../express/types';
import { saveVersion } from '../../versions/saveVersion';
import { uploadFiles } from '../../uploads/uploadFiles';
@@ -56,7 +56,6 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
depth,
collection,
collection: {
Model,
config: collectionConfig,
},
where,
@@ -243,26 +242,13 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
// /////////////////////////////////////
if (!shouldSaveDraft) {
try {
result = await Model.findByIdAndUpdate(
{ _id: id },
result,
{ new: true },
);
} 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)
: error;
result = await req.payload.db.updateOne({
collection: collectionConfig.slug,
locale,
id,
data: result,
});
}
}
result = JSON.parse(JSON.stringify(result));
result.id = result._id as string | number;
result = sanitizeInternalFields(result);
// /////////////////////////////////////
// Create version

View File

@@ -1,11 +1,9 @@
import httpStatus from 'http-status';
import { Config as GeneratedTypes } from 'payload/generated-types';
import { DeepPartial } from 'ts-essentials';
import { Document } from '../../types';
import { Collection } from '../config/types';
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
import executeAccess from '../../auth/executeAccess';
import { APIError, Forbidden, NotFound, ValidationError } from '../../errors';
import { APIError, Forbidden, NotFound } from '../../errors';
import { PayloadRequest } from '../../express/types';
import { hasWhereAccessResult } from '../../auth/types';
import { saveVersion } from '../../versions/saveVersion';
@@ -20,6 +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';
export type Arguments<T extends { [field: string | number | symbol]: unknown }> = {
collection: Collection
@@ -57,7 +56,6 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
depth,
collection,
collection: {
Model,
config: collectionConfig,
},
id,
@@ -85,7 +83,6 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
const { password } = data;
const shouldSaveDraft = Boolean(draftArg && collectionConfig.versions.drafts);
const shouldSavePassword = Boolean(password && collectionConfig.auth && !shouldSaveDraft);
const lean = !shouldSavePassword;
// /////////////////////////////////////
// Access
@@ -98,26 +95,24 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
// Retrieve document
// /////////////////////////////////////
const query = await Model.buildQuery({
where: combineQueries({ id: { equals: id } }, accessResults),
payload,
locale,
});
const doc = await getLatestCollectionVersion({
const findArgs: FindArgs = {
collection: collectionConfig.slug,
where: combineQueries({ id: { equals: id } }, accessResults),
locale,
limit: 1,
};
const docWithLocales = await getLatestCollectionVersion({
payload,
Model,
config: collectionConfig,
id,
query,
lean,
query: findArgs,
});
if (!doc && !hasWherePolicy) throw new NotFound(t);
if (!doc && hasWherePolicy) throw new Forbidden(t);
if (!docWithLocales && !hasWherePolicy) throw new NotFound(t);
if (!docWithLocales && hasWherePolicy) throw new Forbidden(t);
let docWithLocales: Document = JSON.stringify(lean ? doc : doc.toJSON({ virtuals: true }));
docWithLocales = JSON.parse(docWithLocales);
const originalDoc = await afterRead({
depth: 0,
@@ -147,7 +142,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
// Delete any associated files
// /////////////////////////////////////
await deleteAssociatedFiles({ config, collectionConfig, files: filesToUpload, doc, t, overrideDelete: false });
await deleteAssociatedFiles({ config, collectionConfig, files: filesToUpload, doc: docWithLocales, t, overrideDelete: false });
// /////////////////////////////////////
// beforeValidate - Fields
@@ -235,23 +230,13 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
// /////////////////////////////////////
if (!shouldSaveDraft) {
try {
result = await Model.findByIdAndUpdate(
{ _id: id },
dataToUpdate,
{ new: true },
);
} 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)
: error;
result = await req.payload.db.updateOne({
collection: collectionConfig.slug,
locale,
id,
data: dataToUpdate,
});
}
}
result = JSON.parse(JSON.stringify(result));
result.id = result._id as string | number;
result = sanitizeInternalFields(result);
// /////////////////////////////////////
// Create version

View File

@@ -25,8 +25,10 @@ import type {
UploadField,
} from '../fields/config/types';
import type { TypeWithID } from '../collections/config/types';
import type { TypeWithID as GlobalsTypeWithID } from '../globals/config/types';
import type { Payload } from '../payload';
import type { Document, Where } from '../types';
import type { TypeWithVersion } from '../versions/types';
export interface DatabaseAdapter {
/**
@@ -116,12 +118,12 @@ export interface DatabaseAdapter {
// operations
find: <T = TypeWithID>(args: FindArgs) => Promise<PaginatedDocs<T>>;
// TODO: ADD findGlobal method
findVersions: <T = TypeWithID>(args: FindVersionArgs) => Promise<PaginatedDocs<T>>;
findGlobalVersions: <T = TypeWithID>(args: FindGlobalVersionArgs) => Promise<PaginatedDocs<T>>;
findGlobal: FindGlobal;
findVersions: <T = TypeWithID>(args: FindVersionArgs) => Promise<PaginatedDocs<TypeWithVersion<T>>>;
findGlobalVersions: <T = TypeWithID>(args: FindGlobalVersionArgs) => Promise<PaginatedDocs<TypeWithVersion<T>>>;
findOne: FindOne;
create: Create;
update: Update;
updateOne: UpdateOne;
deleteOne: DeleteOne;
deleteMany: DeleteMany;
@@ -134,7 +136,7 @@ export type QueryDraftsArgs = {
limit?: number
pagination?: boolean
sortProperty?: string
sortOrder?: string
sortOrder?: SortArgs
locale?: string
}
@@ -144,10 +146,11 @@ export type FindArgs = {
page?: number
skip?: number
versions?: boolean
/** Setting limit to 1 is equal to the previous Model.findOne(). Setting limit to 0 disables the limit */
limit?: number
pagination?: boolean
sortProperty?: string
sortOrder?: string
sortOrder?: SortArgs
locale?: string
}
@@ -160,7 +163,7 @@ export type FindVersionArgs = {
limit?: number
pagination?: boolean
sortProperty?: string
sortOrder?: string
sortOrder?: SortArgs
locale?: string
}
@@ -173,10 +176,19 @@ export type FindGlobalVersionArgs = {
limit?: number
pagination?: boolean
sortProperty?: string
sortOrder?: string
sortOrder?: SortArgs
locale?: string
}
export type FindGlobalArgs = {
slug: string
locale?: string
where: Where
}
type FindGlobal = <T extends GlobalsTypeWithID = any>(args: FindGlobalArgs) => Promise<T>
export type FindOneArgs = {
collection: string
where: Where
@@ -186,6 +198,7 @@ export type FindOneArgs = {
}
}
type FindOne = (args: FindOneArgs) => Promise<PaginatedDocs>
export type CreateArgs = {
@@ -195,30 +208,18 @@ export type CreateArgs = {
type Create = (args: CreateArgs) => Promise<Document>
type UpdateArgs = {
collection: string
data: Record<string, unknown>
where: Where
draft?: boolean
locale?: string
}
type Update = (args: UpdateArgs) => Promise<Document>
type UpdateOneArgs = {
export type UpdateOneArgs = {
collection: string,
data: Record<string, unknown>,
where: Where,
draft?: boolean
id: string | number,
locale?: string
}
type UpdateOne = (args: UpdateOneArgs) => Promise<Document>
type DeleteOneArgs = {
export type DeleteOneArgs = {
collection: string,
data: Record<string, unknown>,
where: Where,
id: string | number,
}
type DeleteOne = (args: DeleteOneArgs) => Promise<Document>
@@ -252,7 +253,7 @@ export type BuildSortParam = (args: {
locale: string
}) => {
sortProperty: string
sortOrder: string
sortOrder: SortArgs
}
export type PaginatedDocs<T = any> = {
@@ -326,3 +327,5 @@ export type FieldGenerator<TSchema, TField> = {
config: SanitizedConfig,
options: BuildSchemaOptions,
}
export type SortArgs = '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 { Document } from '../types';
import { FindArgs } 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: unknown) => Document;
[slug: string]: (q: FindArgs) => Promise<TypeWithID>;
};
};

View File

@@ -1,6 +1,5 @@
import executeAccess from '../../auth/executeAccess';
import { AccessResult } from '../../config/types';
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
import replaceWithDraftIfAvailable from '../../versions/drafts/replaceWithDraftIfAvailable';
import { afterRead } from '../../fields/hooks/afterRead';
import { SanitizedGlobalConfig } from '../config/types';
@@ -23,7 +22,6 @@ async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T
globalConfig,
req,
req: {
payload,
locale,
},
slug,
@@ -33,8 +31,6 @@ async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T
overrideAccess = false,
} = args;
const { globals: { Model } } = payload;
// /////////////////////////////////////
// Retrieve and execute access
// /////////////////////////////////////
@@ -45,30 +41,15 @@ async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T
accessResult = await executeAccess({ req }, globalConfig.access.read);
}
const query = await Model.buildQuery({
where: combineQueries({ globalType: { equals: slug } }, accessResult),
payload,
locale,
overrideAccess,
globalSlug: slug,
});
// /////////////////////////////////////
// Perform database operation
// /////////////////////////////////////
let doc = await Model.findOne(query).lean() as any;
if (!doc) {
doc = {};
} else if (doc._id) {
doc.id = doc._id;
delete doc._id;
}
doc = JSON.stringify(doc);
doc = JSON.parse(doc);
doc = sanitizeInternalFields(doc);
let doc = await req.payload.db.findGlobal({
slug,
locale,
where: overrideAccess ? { globalType: { equals: slug } } : combineQueries({ globalType: { equals: slug } }, accessResult),
});
// /////////////////////////////////////
// Replace document with draft if available
@@ -76,7 +57,6 @@ async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T
if (globalConfig.versions?.drafts && draftEnabled) {
doc = await replaceWithDraftIfAvailable({
payload,
entity: globalConfig,
entityType: 'global',
doc,

View File

@@ -1,6 +1,6 @@
import type { MongooseAdapter } from '.';
import { CreateArgs } from '../database/types';
import { Document } from '../../types';
import { Document } from '../types';
export async function create<T = unknown>(
this: MongooseAdapter,
@@ -16,6 +16,8 @@ export async function create<T = unknown>(
// custom id type reset
result.id = result._id;
result = JSON.parse(JSON.stringify(result));
if (verificationToken) {
result._verificationToken = verificationToken;
}
return result;
}

24
src/mongoose/deleteOne.ts Normal file
View File

@@ -0,0 +1,24 @@
import type { MongooseAdapter } from '.';
import type { DeleteOneArgs } from '../database/types';
import sanitizeInternalFields from '../utilities/sanitizeInternalFields';
import { Document } from '../types';
export async function deleteOne<T = unknown>(
this: MongooseAdapter,
{ collection, id }: DeleteOneArgs,
): Promise<T> {
const Model = this.collections[collection];
const doc = await Model.findOneAndDelete({ _id: id });
let result: Document = doc.toJSON({ virtuals: true });
// 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,33 @@
import type { MongooseAdapter } from '.';
import { FindGlobalArgs } 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> {
const Model = this.globals;
const query = await Model.buildQuery({
where,
payload: this.payload,
locale,
globalSlug: slug,
});
let doc = await Model.findOne(query).lean() as any;
if (!doc) {
doc = {};
} else if (doc._id) {
doc.id = doc._id;
delete doc._id;
}
doc = JSON.parse(JSON.stringify(doc));
doc = sanitizeInternalFields(doc);
return doc;
}

View File

@@ -3,11 +3,12 @@ import { PaginatedDocs } from './types';
import { FindGlobalVersionArgs } 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, sortProperty, sortOrder, locale, pagination, skip }: FindGlobalVersionArgs,
): Promise<PaginatedDocs<T>> {
): Promise<PaginatedDocs<TypeWithVersion<T>>> {
const Model = this.versions[global];
let useEstimatedCount = false;

View File

@@ -3,11 +3,12 @@ import { PaginatedDocs } from './types';
import { FindVersionArgs } 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, sortProperty, sortOrder, locale, pagination, skip }: FindVersionArgs,
): Promise<PaginatedDocs<T>> {
): Promise<PaginatedDocs<TypeWithVersion<T>>> {
const Model = this.versions[collection];
let useEstimatedCount = false;

View File

@@ -8,9 +8,12 @@ import { queryDrafts } from './queryDrafts';
import { GlobalModel } from '../globals/config/types';
import { find } from './find';
import { create } from './create';
import { updateOne } from './updateOne';
import { deleteOne } from './deleteOne';
import { findVersions } from './findVersions';
import { findGlobalVersions } from './findGlobalVersions';
import type { Payload } from '../index';
import { findGlobal } from './findGlobal';
export interface Args {
payload: Payload,
@@ -59,7 +62,10 @@ export function mongooseAdapter({ payload, url, connectOptions }: Args): Mongoos
queryDrafts,
find,
findVersions,
findGlobal,
findGlobalVersions,
create,
updateOne,
deleteOne,
};
}

View File

@@ -2,7 +2,6 @@
import mongoose from 'mongoose';
import paginate from 'mongoose-paginate-v2';
import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2';
import { SanitizedConfig } from 'payload/config';
import { buildVersionCollectionFields } from '../versions/buildCollectionFields';
import getBuildQueryPlugin from './queries/buildQuery';
import buildCollectionSchema from './models/buildCollectionSchema';
@@ -12,6 +11,8 @@ 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';
export async function init(
this: MongooseAdapter,

View File

@@ -1,6 +1,7 @@
import { Config } from '../../config/types';
import { getLocalizedSortProperty } from './getLocalizedSortProperty';
import { Field } from '../../fields/config/types';
import type { SortArgs } from '../../database/types';
type Args = {
sort: string
@@ -10,9 +11,9 @@ type Args = {
locale: string
}
export const buildSortParam = ({ sort, config, fields, timestamps, locale }: Args): [string, string] => {
export const buildSortParam = ({ sort, config, fields, timestamps, locale }: Args): [string, SortArgs] => {
let sortProperty: string;
let sortOrder = 'desc';
let sortOrder: SortArgs = 'desc';
if (!sort) {
if (timestamps) {

36
src/mongoose/updateOne.ts Normal file
View File

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

View File

@@ -233,7 +233,7 @@ export class BasePayload<TGeneratedTypes extends GeneratedTypes> {
registerGraphQLSchema(this);
}
if (this.db.init) {
if (this.db?.init) {
await this.db?.init({ config: this.config });
}

View File

@@ -2,14 +2,14 @@ import { Payload } from '../../payload';
import { docHasTimestamps, PayloadRequest, Where } from '../../types';
import { hasWhereAccessResult } from '../../auth';
import { AccessResult } from '../../config/types';
import { CollectionModel, SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types';
import { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types';
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';
type Arguments<T> = {
payload: Payload
entity: SanitizedCollectionConfig | SanitizedGlobalConfig
entityType: 'collection' | 'global'
doc: T
@@ -19,15 +19,12 @@ type Arguments<T> = {
}
const replaceWithDraftIfAvailable = async <T extends TypeWithID>({
payload,
entity,
entityType,
doc,
req,
accessResult,
}: Arguments<T>): Promise<T> => {
const VersionModel = payload.versions[entity.slug] as CollectionModel;
const queryToBuild: Where = {
and: [
{
@@ -60,17 +57,21 @@ const replaceWithDraftIfAvailable = async <T extends TypeWithID>({
versionAccessResult = appendVersionToQueryKey(accessResult);
}
const query = await VersionModel.buildQuery({
where: combineQueries(queryToBuild, versionAccessResult),
payload,
locale: req.locale,
globalSlug: entityType === 'global' ? entity.slug : undefined,
});
let draft = await VersionModel.findOne(query, {}, {
lean: true,
sort: { updatedAt: 'desc' },
});
const findVersionArgs: FindVersionArgs = {
locale: req.locale,
where: combineQueries(queryToBuild, versionAccessResult),
collection: entity.slug,
limit: 1,
sortProperty: 'updatedAt',
sortOrder: 'desc',
};
const { docs: versionDocs } = await req.payload.db.findVersions<T>(findVersionArgs);
let draft = versionDocs[0];
if (!draft) {
return doc;

View File

@@ -1,39 +1,39 @@
import { docHasTimestamps } from '../types';
import { Payload } from '../payload';
import { CollectionModel, SanitizedCollectionConfig, TypeWithID } from '../collections/config/types';
import { SanitizedCollectionConfig, TypeWithID } from '../collections/config/types';
import { TypeWithVersion } from './types';
import { FindArgs } from '../database/types';
type Args = {
payload: Payload
query: Record<string, unknown>
lean?: boolean
query: FindArgs
id: string | number
Model: CollectionModel
config: SanitizedCollectionConfig
}
export const getLatestCollectionVersion = async <T extends TypeWithID = any>({
payload,
config,
Model,
query,
id,
lean = true,
}: Args): Promise<T> => {
let latestVersion;
let latestVersion: TypeWithVersion<T>;
if (config.versions?.drafts) {
latestVersion = await payload.versions[config.slug].findOne({
parent: id,
}, {}, {
sort: { updatedAt: 'desc' },
lean,
const { docs } = await payload.db.findVersions<T>({
collection: config.slug,
where: { parent: { equals: id } },
sortProperty: 'updatedAt',
sortOrder: 'desc',
});
[latestVersion] = docs;
}
const doc = await Model.findOne(query, {}, { lean });
const { docs } = await payload.db.find<T>(query);
const [doc] = docs;
if (!latestVersion || (docHasTimestamps(doc) && latestVersion.updatedAt < doc.updatedAt)) {
doc.id = doc._id;
return doc;
}

View File

@@ -50,10 +50,22 @@ export const saveVersion = async ({
if (autosave) {
const query: FilterQuery<unknown> = {};
if (collection) query.parent = id;
const latestVersion = await VersionModel.findOne(query, {}, { sort: { updatedAt: 'desc' } });
const { docs } = await payload.db.findVersions({
collection: entityConfig.slug,
limit: 1,
where: {
parent: {
equals: id,
},
},
sortOrder: 'desc',
sortProperty: 'updatedAt',
});
const [latestVersion] = docs;
// overwrite the latest version if it's set to autosave
if (latestVersion?.autosave === true) {
if ((latestVersion as any)?.autosave === true) {
createNewVersion = false;
const data: Record<string, unknown> = {
@@ -64,7 +76,7 @@ export const saveVersion = async ({
result = await VersionModel.findByIdAndUpdate(
{
_id: latestVersion._id,
_id: latestVersion.id,
},
data,
{ new: true, lean: true },

View File

@@ -297,7 +297,7 @@ describe('admin', () => {
await createPost();
await page.locator('.list-controls__toggle-columns').click();
await wait(500); // Wait for column toggle UI, should probably use waitForSelector
await page.waitForSelector('.column-selector'); // Waiting for column toggle UI
const numberOfColumns = await page.locator(columnCountLocator).count();
await expect(await page.locator('table >> thead >> tr >> th:nth-child(2)')).toHaveText('ID');
@@ -306,13 +306,13 @@ describe('admin', () => {
// Remove ID column
await idButton.click();
await wait(100);
await wait(200);
await expect(await page.locator(columnCountLocator)).toHaveCount(numberOfColumns - 1);
await expect(await page.locator('table >> thead >> tr >> th:nth-child(2)')).toHaveText('Number');
// Add back ID column
await idButton.click();
await wait(100);
await wait(200);
await expect(await page.locator(columnCountLocator)).toHaveCount(numberOfColumns);
await expect(await page.locator('table >> thead >> tr >> th:nth-child(2)')).toHaveText('ID');
});

View File

@@ -4,9 +4,24 @@ import { generateGraphQLSchema } from '../src/bin/generateGraphQLSchema';
const [testConfigDir] = process.argv.slice(2);
const testDir = path.resolve(__dirname, testConfigDir);
let testDir;
if (testConfigDir) {
testDir = path.resolve(__dirname, testConfigDir);
setPaths(testDir);
generateGraphQLSchema();
} else {
// Generate graphql schema for entire directory
testDir = __dirname;
fs.readdirSync(__dirname, { withFileTypes: true })
.filter((f) => f.isDirectory())
.forEach((dir) => {
const suiteDir = path.resolve(testDir, dir.name);
const configFound = setPaths(suiteDir);
if (configFound) generateGraphQLSchema();
});
}
// Set config path and TS output path using test dir
function setPaths(dir) {