feat: detaches localization from mongoose entirely
This commit is contained in:
@@ -177,7 +177,7 @@ async function login(incomingArgs: Arguments): Promise<Result> {
|
||||
hook: 'afterRead',
|
||||
operation: 'login',
|
||||
overrideAccess,
|
||||
reduceLocales: true,
|
||||
flattenLocales: true,
|
||||
showHiddenFields,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import paginate from 'mongoose-paginate-v2';
|
||||
import buildQueryPlugin from '../mongoose/buildQuery';
|
||||
import localizationPlugin from '../localization/plugin';
|
||||
import buildSchema from '../mongoose/buildSchema';
|
||||
|
||||
const buildCollectionSchema = (collection, config, schemaOptions = {}) => {
|
||||
@@ -9,10 +8,6 @@ const buildCollectionSchema = (collection, config, schemaOptions = {}) => {
|
||||
schema.plugin(paginate)
|
||||
.plugin(buildQueryPlugin);
|
||||
|
||||
if (config.localization) {
|
||||
schema.plugin(localizationPlugin, config.localization);
|
||||
}
|
||||
|
||||
return schema;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ import { DeepRequired } from 'ts-essentials';
|
||||
import { PaginateModel, PassportLocalModel } from 'mongoose';
|
||||
import { Access } from '../../config/types';
|
||||
import { Field } from '../../fields/config/types';
|
||||
import { Document, PayloadMongooseDocument } from '../../types';
|
||||
import { Document } from '../../types';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import { IncomingAuthType, Auth } from '../../auth/types';
|
||||
import { IncomingUploadType, Upload } from '../../uploads/types';
|
||||
|
||||
export interface CollectionModel extends PaginateModel<PayloadMongooseDocument>, PassportLocalModel<PayloadMongooseDocument> {
|
||||
export interface CollectionModel extends PaginateModel<any>, PassportLocalModel<any> {
|
||||
buildQuery: (query: unknown, locale?: string) => Record<string, unknown>
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export type Arguments = {
|
||||
}
|
||||
|
||||
async function create(this: Payload, incomingArgs: Arguments): Promise<Document> {
|
||||
const { performFieldOperations, config, emailOptions } = this;
|
||||
const { config, emailOptions } = this;
|
||||
|
||||
let args = incomingArgs;
|
||||
|
||||
@@ -54,10 +54,6 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
|
||||
config: collectionConfig,
|
||||
},
|
||||
req,
|
||||
req: {
|
||||
locale,
|
||||
fallbackLocale,
|
||||
},
|
||||
disableVerificationEmail,
|
||||
depth,
|
||||
overrideAccess,
|
||||
@@ -100,18 +96,6 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
|
||||
})) || data;
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
hook: 'beforeChange',
|
||||
operation: 'create',
|
||||
req,
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Collection
|
||||
// /////////////////////////////////////
|
||||
@@ -127,9 +111,21 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Upload and resize potential files
|
||||
// beforeChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
let resultWithLocales = await this.performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
hook: 'beforeChange',
|
||||
operation: 'create',
|
||||
req,
|
||||
overrideAccess,
|
||||
unflattenLocales: true,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Upload and resize potential files
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (collectionConfig.upload) {
|
||||
const fileData: Partial<FileData> = {};
|
||||
@@ -174,8 +170,8 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
|
||||
fileData.filesize = file.size;
|
||||
fileData.mimeType = file.mimetype;
|
||||
|
||||
data = {
|
||||
...data,
|
||||
resultWithLocales = {
|
||||
...resultWithLocales,
|
||||
...fileData,
|
||||
};
|
||||
}
|
||||
@@ -184,28 +180,22 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
|
||||
// Create
|
||||
// /////////////////////////////////////
|
||||
|
||||
let doc = new Model();
|
||||
|
||||
if (locale && doc.setLocale) {
|
||||
doc.setLocale(locale, fallbackLocale);
|
||||
}
|
||||
|
||||
if (collectionConfig.auth) {
|
||||
if (data.email) {
|
||||
data.email = (data.email as string).toLowerCase();
|
||||
resultWithLocales.email = (data.email as string).toLowerCase();
|
||||
}
|
||||
if (collectionConfig.auth.verify) {
|
||||
data._verified = false;
|
||||
data._verificationToken = crypto.randomBytes(20).toString('hex');
|
||||
resultWithLocales._verified = false;
|
||||
resultWithLocales._verificationToken = crypto.randomBytes(20).toString('hex');
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(doc, data);
|
||||
let doc;
|
||||
|
||||
if (collectionConfig.auth) {
|
||||
doc = await Model.register(doc, data.password as string);
|
||||
doc = await Model.register(resultWithLocales, data.password as string);
|
||||
} else {
|
||||
await doc.save();
|
||||
doc = await Model.create(resultWithLocales);
|
||||
}
|
||||
|
||||
let result: Document = doc.toJSON({ virtuals: true });
|
||||
@@ -218,7 +208,7 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
|
||||
// afterChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await performFieldOperations(collectionConfig, {
|
||||
result = await this.performFieldOperations(collectionConfig, {
|
||||
data: result,
|
||||
hook: 'afterChange',
|
||||
operation: 'create',
|
||||
@@ -270,7 +260,7 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
|
||||
hook: 'afterRead',
|
||||
operation: 'create',
|
||||
overrideAccess,
|
||||
reduceLocales: false,
|
||||
flattenLocales: true,
|
||||
showHiddenFields,
|
||||
});
|
||||
|
||||
|
||||
@@ -94,10 +94,6 @@ async function deleteQuery(incomingArgs: Arguments): Promise<Document> {
|
||||
if (!docToDelete && !hasWhereAccess) throw new NotFound();
|
||||
if (!docToDelete && hasWhereAccess) throw new Forbidden();
|
||||
|
||||
if (locale && docToDelete.setLocale) {
|
||||
docToDelete.setLocale(locale, fallbackLocale);
|
||||
}
|
||||
|
||||
const resultToDelete = docToDelete.toJSON({ virtuals: true });
|
||||
|
||||
// /////////////////////////////////////
|
||||
@@ -137,10 +133,6 @@ async function deleteQuery(incomingArgs: Arguments): Promise<Document> {
|
||||
|
||||
const doc = await Model.findOneAndDelete({ _id: id });
|
||||
|
||||
if (locale && doc.setLocale) {
|
||||
doc.setLocale(locale, fallbackLocale);
|
||||
}
|
||||
|
||||
let result: Document = doc.toJSON({ virtuals: true });
|
||||
|
||||
result = removeInternalFields(result);
|
||||
@@ -169,7 +161,7 @@ async function deleteQuery(incomingArgs: Arguments): Promise<Document> {
|
||||
hook: 'afterRead',
|
||||
operation: 'delete',
|
||||
overrideAccess,
|
||||
reduceLocales: false,
|
||||
flattenLocales: true,
|
||||
showHiddenFields,
|
||||
});
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ async function find(incomingArgs: Arguments): Promise<PaginatedDocs> {
|
||||
hook: 'afterRead',
|
||||
operation: 'read',
|
||||
overrideAccess,
|
||||
reduceLocales: true,
|
||||
flattenLocales: true,
|
||||
showHiddenFields,
|
||||
},
|
||||
find,
|
||||
|
||||
@@ -141,7 +141,7 @@ async function findByID(incomingArgs: Arguments): Promise<Document> {
|
||||
operation: 'read',
|
||||
currentDepth,
|
||||
overrideAccess,
|
||||
reduceLocales: true,
|
||||
flattenLocales: true,
|
||||
showHiddenFields,
|
||||
});
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ export default async function create(options: Options): Promise<Document> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
locale,
|
||||
fallbackLocale,
|
||||
locale = this?.config?.localization?.defaultLocale,
|
||||
fallbackLocale = null,
|
||||
data,
|
||||
user,
|
||||
overrideAccess = true,
|
||||
|
||||
@@ -16,8 +16,8 @@ export default async function localDelete(options: Options): Promise<Document> {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
id,
|
||||
locale,
|
||||
fallbackLocale,
|
||||
locale = this?.config?.localization?.defaultLocale,
|
||||
fallbackLocale = null,
|
||||
user,
|
||||
overrideAccess = true,
|
||||
showHiddenFields,
|
||||
|
||||
@@ -22,8 +22,8 @@ export default async function find(options: Options): Promise<PaginatedDocs> {
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
locale,
|
||||
fallbackLocale,
|
||||
locale = this?.config?.localization?.defaultLocale,
|
||||
fallbackLocale = null,
|
||||
user,
|
||||
overrideAccess = true,
|
||||
showHiddenFields,
|
||||
|
||||
@@ -17,8 +17,8 @@ export default async function findByID(options: Options): Promise<Document> {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
id,
|
||||
locale,
|
||||
fallbackLocale,
|
||||
locale = this?.config?.localization?.defaultLocale,
|
||||
fallbackLocale = null,
|
||||
user,
|
||||
overrideAccess = true,
|
||||
disableErrors = false,
|
||||
|
||||
@@ -18,8 +18,8 @@ export default async function update(options: Options): Promise<Document> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
locale,
|
||||
fallbackLocale,
|
||||
locale = this?.config?.localization?.defaultLocale,
|
||||
fallbackLocale = null,
|
||||
data,
|
||||
id,
|
||||
user,
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import httpStatus from 'http-status';
|
||||
import deepmerge from 'deepmerge';
|
||||
import merge from 'lodash.merge';
|
||||
import httpStatus from 'http-status';
|
||||
import path from 'path';
|
||||
import { UploadedFile } from 'express-fileupload';
|
||||
import { Where, Document } from '../../types';
|
||||
import { Collection } from '../config/types';
|
||||
|
||||
import removeInternalFields from '../../utilities/removeInternalFields';
|
||||
import overwriteMerge from '../../utilities/overwriteMerge';
|
||||
import removeInternalFields from '../../utilities/removeInternalFields';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import { NotFound, Forbidden, APIError, FileUploadError } from '../../errors';
|
||||
import isImage from '../../uploads/isImage';
|
||||
@@ -60,7 +59,6 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
req,
|
||||
req: {
|
||||
locale,
|
||||
fallbackLocale,
|
||||
},
|
||||
overrideAccess,
|
||||
showHiddenFields,
|
||||
@@ -104,14 +102,20 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
if (!doc && !hasWherePolicy) throw new NotFound();
|
||||
if (!doc && hasWherePolicy) throw new Forbidden();
|
||||
|
||||
if (locale && doc.setLocale) {
|
||||
doc.setLocale(locale, null);
|
||||
}
|
||||
let docWithLocales: Document = doc.toJSON({ virtuals: true });
|
||||
docWithLocales = JSON.stringify(docWithLocales);
|
||||
docWithLocales = JSON.parse(docWithLocales);
|
||||
|
||||
let originalDoc: Document = doc.toJSON({ virtuals: true });
|
||||
|
||||
originalDoc = JSON.stringify(originalDoc);
|
||||
originalDoc = JSON.parse(originalDoc);
|
||||
const originalDoc = await performFieldOperations(collectionConfig, {
|
||||
depth,
|
||||
req,
|
||||
data: docWithLocales,
|
||||
hook: 'afterRead',
|
||||
operation: 'update',
|
||||
overrideAccess,
|
||||
flattenLocales: true,
|
||||
showHiddenFields,
|
||||
});
|
||||
|
||||
let { data } = args;
|
||||
|
||||
@@ -144,20 +148,6 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
})) || data;
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
req,
|
||||
id,
|
||||
originalDoc,
|
||||
hook: 'beforeChange',
|
||||
operation: 'update',
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Collection
|
||||
// /////////////////////////////////////
|
||||
@@ -179,6 +169,22 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
|
||||
data = deepmerge(originalDoc, data, { arrayMerge: overwriteMerge });
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
let result = await performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
req,
|
||||
id,
|
||||
originalDoc,
|
||||
hook: 'beforeChange',
|
||||
operation: 'update',
|
||||
overrideAccess,
|
||||
unflattenLocales: true,
|
||||
docWithLocales,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Upload and resize potential files
|
||||
// /////////////////////////////////////
|
||||
@@ -219,13 +225,13 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
throw new FileUploadError();
|
||||
}
|
||||
|
||||
data = {
|
||||
...data,
|
||||
result = {
|
||||
...result,
|
||||
...fileData,
|
||||
};
|
||||
} else if (data.file === null) {
|
||||
data = {
|
||||
...data,
|
||||
} else if (result.file === null) {
|
||||
result = {
|
||||
...result,
|
||||
filename: null,
|
||||
sizes: null,
|
||||
};
|
||||
@@ -241,26 +247,52 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
if (password) {
|
||||
await doc.setPassword(password as string);
|
||||
delete data.password;
|
||||
delete result.password;
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Update
|
||||
// /////////////////////////////////////
|
||||
|
||||
merge(doc, data);
|
||||
|
||||
await doc.save();
|
||||
|
||||
if (locale && doc.setLocale) {
|
||||
doc.setLocale(locale, fallbackLocale);
|
||||
}
|
||||
|
||||
let result: Document = doc.toJSON({ virtuals: true });
|
||||
result = await Model.findByIdAndUpdate(
|
||||
{ _id: id },
|
||||
result,
|
||||
{ overwrite: true, new: true },
|
||||
);
|
||||
|
||||
result = result.toJSON({ virtuals: true });
|
||||
result = removeInternalFields(result);
|
||||
result = JSON.stringify(result);
|
||||
result = JSON.parse(result);
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await performFieldOperations(collectionConfig, {
|
||||
depth,
|
||||
req,
|
||||
data: result,
|
||||
hook: 'afterRead',
|
||||
operation: 'update',
|
||||
overrideAccess,
|
||||
flattenLocales: true,
|
||||
showHiddenFields,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook;
|
||||
|
||||
result = await hook({
|
||||
req,
|
||||
doc: result,
|
||||
}) || result;
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterChange - Fields
|
||||
// /////////////////////////////////////
|
||||
@@ -290,34 +322,6 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
}) || result;
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await performFieldOperations(collectionConfig, {
|
||||
depth,
|
||||
req,
|
||||
data: result,
|
||||
hook: 'afterRead',
|
||||
operation: 'update',
|
||||
overrideAccess,
|
||||
reduceLocales: false,
|
||||
showHiddenFields,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook;
|
||||
|
||||
result = await hook({
|
||||
req,
|
||||
doc: result,
|
||||
}) || result;
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Return results
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Payload } from '..';
|
||||
import { ValidationError } from '../errors';
|
||||
import sanitizeFallbackLocale from '../localization/sanitizeFallbackLocale';
|
||||
import traverseFields from './traverseFields';
|
||||
@@ -6,25 +7,29 @@ import { GlobalConfig } from '../globals/config/types';
|
||||
import { Operation } from '../types';
|
||||
import { PayloadRequest } from '../express/types';
|
||||
import { HookName } from './config/types';
|
||||
import deepCopyObject from '../utilities/deepCopyObject';
|
||||
|
||||
type Arguments = {
|
||||
data: Record<string, unknown>
|
||||
operation: Operation
|
||||
hook: HookName
|
||||
hook?: HookName
|
||||
req: PayloadRequest
|
||||
overrideAccess: boolean
|
||||
reduceLocales?: boolean
|
||||
flattenLocales?: boolean
|
||||
unflattenLocales?: boolean
|
||||
originalDoc?: Record<string, unknown>
|
||||
docWithLocales?: Record<string, unknown>
|
||||
id?: string
|
||||
showHiddenFields?: boolean
|
||||
depth?: number
|
||||
currentDepth?: number
|
||||
}
|
||||
|
||||
export default async function performFieldOperations(entityConfig: CollectionConfig | GlobalConfig, args: Arguments): Promise<{ [key: string]: unknown }> {
|
||||
export default async function performFieldOperations(this: Payload, entityConfig: CollectionConfig | GlobalConfig, args: Arguments): Promise<{ [key: string]: unknown }> {
|
||||
const {
|
||||
data: fullData,
|
||||
data,
|
||||
originalDoc: fullOriginalDoc,
|
||||
docWithLocales,
|
||||
operation,
|
||||
hook,
|
||||
req,
|
||||
@@ -34,10 +39,13 @@ export default async function performFieldOperations(entityConfig: CollectionCon
|
||||
locale,
|
||||
},
|
||||
overrideAccess,
|
||||
reduceLocales,
|
||||
flattenLocales,
|
||||
unflattenLocales = false,
|
||||
showHiddenFields = false,
|
||||
} = args;
|
||||
|
||||
const fullData = deepCopyObject(data);
|
||||
|
||||
const fallbackLocale = sanitizeFallbackLocale(req.fallbackLocale);
|
||||
|
||||
let depth = 0;
|
||||
@@ -57,6 +65,7 @@ export default async function performFieldOperations(entityConfig: CollectionCon
|
||||
const accessPromises = [];
|
||||
const relationshipPopulations = [];
|
||||
const hookPromises = [];
|
||||
const unflattenLocaleActions = [];
|
||||
const errors: { message: string, field: string }[] = [];
|
||||
|
||||
// //////////////////////////////////////////
|
||||
@@ -64,11 +73,11 @@ export default async function performFieldOperations(entityConfig: CollectionCon
|
||||
// //////////////////////////////////////////
|
||||
|
||||
traverseFields({
|
||||
fields: entityConfig.fields, // TODO: Bad typing, this exists
|
||||
fields: entityConfig.fields,
|
||||
data: fullData,
|
||||
originalDoc: fullOriginalDoc,
|
||||
path: '',
|
||||
reduceLocales,
|
||||
flattenLocales,
|
||||
locale,
|
||||
fallbackLocale,
|
||||
accessPromises,
|
||||
@@ -87,6 +96,9 @@ export default async function performFieldOperations(entityConfig: CollectionCon
|
||||
errors,
|
||||
payload: this,
|
||||
showHiddenFields,
|
||||
unflattenLocales,
|
||||
unflattenLocaleActions,
|
||||
docWithLocales,
|
||||
});
|
||||
|
||||
await Promise.all(hookPromises);
|
||||
@@ -99,6 +111,8 @@ export default async function performFieldOperations(entityConfig: CollectionCon
|
||||
throw new ValidationError(errors);
|
||||
}
|
||||
|
||||
unflattenLocaleActions.forEach((action) => action());
|
||||
|
||||
await Promise.all(accessPromises);
|
||||
|
||||
const relationshipPopulationPromises = relationshipPopulations.map((population) => population());
|
||||
|
||||
@@ -11,7 +11,7 @@ type Arguments = {
|
||||
data: Record<string, any>
|
||||
originalDoc: Record<string, any>
|
||||
path: string
|
||||
reduceLocales: boolean
|
||||
flattenLocales: boolean
|
||||
locale: string
|
||||
fallbackLocale: string
|
||||
accessPromises: Promise<void>[]
|
||||
@@ -30,6 +30,9 @@ type Arguments = {
|
||||
errors: {message: string, field: string}[]
|
||||
payload: Payload
|
||||
showHiddenFields: boolean
|
||||
unflattenLocales: boolean
|
||||
unflattenLocaleActions: (() => void)[]
|
||||
docWithLocales?: Record<string, any>
|
||||
}
|
||||
|
||||
const traverseFields = (args: Arguments): void => {
|
||||
@@ -38,7 +41,7 @@ const traverseFields = (args: Arguments): void => {
|
||||
data = {},
|
||||
originalDoc = {},
|
||||
path,
|
||||
reduceLocales,
|
||||
flattenLocales,
|
||||
locale,
|
||||
fallbackLocale,
|
||||
accessPromises,
|
||||
@@ -57,6 +60,9 @@ const traverseFields = (args: Arguments): void => {
|
||||
errors,
|
||||
payload,
|
||||
showHiddenFields,
|
||||
unflattenLocaleActions,
|
||||
unflattenLocales,
|
||||
docWithLocales = {},
|
||||
} = args;
|
||||
|
||||
fields.forEach((field) => {
|
||||
@@ -85,7 +91,7 @@ const traverseFields = (args: Arguments): void => {
|
||||
&& field.name
|
||||
&& field.localized
|
||||
&& locale !== 'all'
|
||||
&& reduceLocales;
|
||||
&& flattenLocales;
|
||||
|
||||
if (hasLocalizedValue) {
|
||||
let localizedValue = data[field.name][locale];
|
||||
@@ -94,6 +100,15 @@ const traverseFields = (args: Arguments): void => {
|
||||
dataCopy[field.name] = localizedValue;
|
||||
}
|
||||
|
||||
if (field.localized && unflattenLocales) {
|
||||
unflattenLocaleActions.push(() => {
|
||||
data[field.name] = payload.config.localization.locales.reduce((locales, localeID) => ({
|
||||
...locales,
|
||||
[localeID]: localeID === locale ? data[field.name] : docWithLocales?.[field.name]?.[localeID],
|
||||
}), {});
|
||||
});
|
||||
}
|
||||
|
||||
accessPromises.push(accessPromise({
|
||||
data,
|
||||
originalDoc,
|
||||
@@ -128,12 +143,12 @@ const traverseFields = (args: Arguments): void => {
|
||||
} else if (fieldIsArrayType(field)) {
|
||||
if (Array.isArray(data[field.name])) {
|
||||
(data[field.name] as Record<string, unknown>[]).forEach((rowData, i) => {
|
||||
const originalDocRow = originalDoc && originalDoc[field.name] && originalDoc[field.name][i];
|
||||
traverseFields({
|
||||
...args,
|
||||
fields: field.fields,
|
||||
data: rowData,
|
||||
originalDoc: originalDocRow || undefined,
|
||||
originalDoc: originalDoc?.[field.name]?.[i],
|
||||
docWithLocales: docWithLocales?.[field.name]?.[i],
|
||||
path: `${path}${field.name}.${i}.`,
|
||||
});
|
||||
});
|
||||
@@ -144,6 +159,7 @@ const traverseFields = (args: Arguments): void => {
|
||||
fields: field.fields,
|
||||
data: data[field.name] as Record<string, unknown>,
|
||||
originalDoc: originalDoc[field.name],
|
||||
docWithLocales: docWithLocales?.[field.name],
|
||||
path: `${path}${field.name}.`,
|
||||
});
|
||||
}
|
||||
@@ -153,14 +169,14 @@ const traverseFields = (args: Arguments): void => {
|
||||
if (Array.isArray(data[field.name])) {
|
||||
(data[field.name] as Record<string, unknown>[]).forEach((rowData, i) => {
|
||||
const block = field.blocks.find((blockType) => blockType.slug === rowData.blockType);
|
||||
const originalDocRow = originalDoc && originalDoc[field.name] && originalDoc[field.name][i];
|
||||
|
||||
if (block) {
|
||||
traverseFields({
|
||||
...args,
|
||||
fields: block.fields,
|
||||
data: rowData,
|
||||
originalDoc: originalDocRow || undefined,
|
||||
originalDoc: originalDoc?.[field.name]?.[i],
|
||||
docWithLocales: docWithLocales?.[field.name]?.[i],
|
||||
path: `${path}${field.name}.${i}.`,
|
||||
});
|
||||
}
|
||||
@@ -168,7 +184,7 @@ const traverseFields = (args: Arguments): void => {
|
||||
}
|
||||
}
|
||||
|
||||
if ((operation === 'create' || operation === 'update') && field.name) {
|
||||
if (hook === 'beforeChange' && field.name) {
|
||||
const updatedData = data;
|
||||
|
||||
if (data?.[field.name] === undefined && originalDoc?.[field.name] === undefined && field.defaultValue) {
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
import mongoose from 'mongoose';
|
||||
import buildSchema from '../mongoose/buildSchema';
|
||||
import localizationPlugin from '../localization/plugin';
|
||||
import { Config } from '../config/types';
|
||||
|
||||
const buildModel = (config: Config): mongoose.PaginateModel<any> | null => {
|
||||
if (config.globals && config.globals.length > 0) {
|
||||
const globalsSchema = new mongoose.Schema({}, { discriminatorKey: 'globalType', timestamps: true });
|
||||
|
||||
if (config.localization) {
|
||||
globalsSchema.plugin(localizationPlugin, config.localization);
|
||||
}
|
||||
|
||||
const Globals = mongoose.model('globals', globalsSchema);
|
||||
|
||||
Object.values(config.globals).forEach((globalConfig) => {
|
||||
const globalSchema = buildSchema(config, globalConfig.fields, {});
|
||||
|
||||
if (config.localization) {
|
||||
globalSchema.plugin(localizationPlugin, config.localization);
|
||||
}
|
||||
|
||||
Globals.discriminator(globalConfig.slug, globalSchema);
|
||||
});
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ async function findOne(args) {
|
||||
delete doc._id;
|
||||
}
|
||||
|
||||
doc = removeInternalFields(doc);
|
||||
doc = JSON.stringify(doc);
|
||||
doc = JSON.parse(doc);
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 3. Execute before collection hook
|
||||
// /////////////////////////////////////
|
||||
@@ -54,7 +58,7 @@ async function findOne(args) {
|
||||
operation: 'read',
|
||||
req,
|
||||
depth,
|
||||
reduceLocales: true,
|
||||
flattenLocales: true,
|
||||
showHiddenFields,
|
||||
});
|
||||
|
||||
@@ -75,10 +79,6 @@ async function findOne(args) {
|
||||
// 6. Return results
|
||||
// /////////////////////////////////////
|
||||
|
||||
doc = removeInternalFields(doc);
|
||||
doc = JSON.stringify(doc);
|
||||
doc = JSON.parse(doc);
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ async function findOne(options) {
|
||||
const {
|
||||
global: globalSlug,
|
||||
depth,
|
||||
locale,
|
||||
fallbackLocale,
|
||||
locale = this?.config?.localization?.defaultLocale,
|
||||
fallbackLocale = null,
|
||||
user,
|
||||
overrideAccess = true,
|
||||
showHiddenFields,
|
||||
|
||||
@@ -2,8 +2,8 @@ async function update(options) {
|
||||
const {
|
||||
global: globalSlug,
|
||||
depth,
|
||||
locale,
|
||||
fallbackLocale,
|
||||
locale = this?.config?.localization?.defaultLocale,
|
||||
fallbackLocale = null,
|
||||
data,
|
||||
user,
|
||||
overrideAccess = true,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import deepmerge from 'deepmerge';
|
||||
import merge from 'lodash.merge';
|
||||
import overwriteMerge from '../../utilities/overwriteMerge';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import removeInternalFields from '../../utilities/removeInternalFields';
|
||||
@@ -11,10 +10,6 @@ async function update(args) {
|
||||
globalConfig,
|
||||
slug,
|
||||
req,
|
||||
req: {
|
||||
locale,
|
||||
fallbackLocale,
|
||||
},
|
||||
depth,
|
||||
overrideAccess,
|
||||
showHiddenFields,
|
||||
@@ -33,25 +28,48 @@ async function update(args) {
|
||||
// /////////////////////////////////////
|
||||
|
||||
let global = await Model.findOne({ globalType: slug });
|
||||
let globalJSON;
|
||||
|
||||
if (!global) {
|
||||
global = new Model({ globalType: slug });
|
||||
if (global) {
|
||||
globalJSON = global.toJSON({ virtuals: true });
|
||||
globalJSON = JSON.stringify(globalJSON);
|
||||
globalJSON = JSON.parse(globalJSON);
|
||||
|
||||
if (globalJSON._id) {
|
||||
delete globalJSON._id;
|
||||
}
|
||||
} else {
|
||||
globalJSON = { globalType: slug };
|
||||
}
|
||||
|
||||
if (locale && global.setLocale) {
|
||||
global.setLocale(locale, null);
|
||||
}
|
||||
|
||||
const globalJSON = global.toJSON({ virtuals: true });
|
||||
|
||||
if (globalJSON._id) {
|
||||
delete globalJSON._id;
|
||||
}
|
||||
const originalDoc = await this.performFieldOperations(globalConfig, {
|
||||
depth,
|
||||
req,
|
||||
data: globalJSON,
|
||||
hook: 'afterRead',
|
||||
operation: 'update',
|
||||
overrideAccess,
|
||||
flattenLocales: true,
|
||||
showHiddenFields,
|
||||
});
|
||||
|
||||
let { data } = args;
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 3. Execute before validate collection hooks
|
||||
// beforeValidate - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await this.performFieldOperations(globalConfig, {
|
||||
data,
|
||||
req,
|
||||
originalDoc,
|
||||
hook: 'beforeValidate',
|
||||
operation: 'update',
|
||||
overrideAccess,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeValidate - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
|
||||
@@ -60,24 +78,12 @@ async function update(args) {
|
||||
data = (await hook({
|
||||
data,
|
||||
req,
|
||||
originalDoc: globalJSON,
|
||||
originalDoc,
|
||||
})) || data;
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 4. Execute field-level hooks, access, and validation
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await this.performFieldOperations(globalConfig, {
|
||||
data,
|
||||
req,
|
||||
hook: 'beforeChange',
|
||||
operation: 'update',
|
||||
originalDoc: global,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 5. Execute before global hook
|
||||
// beforeChange - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
|
||||
@@ -86,32 +92,51 @@ async function update(args) {
|
||||
data = (await hook({
|
||||
data,
|
||||
req,
|
||||
originalDoc: global,
|
||||
originalDoc,
|
||||
})) || data;
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 6. Merge updates into existing data
|
||||
// Merge updates into existing data
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = deepmerge(globalJSON, data, { arrayMerge: overwriteMerge });
|
||||
data = deepmerge(originalDoc, data, { arrayMerge: overwriteMerge });
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 7. Perform database operation
|
||||
// beforeChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
merge(global, data);
|
||||
const result = await this.performFieldOperations(globalConfig, {
|
||||
data,
|
||||
req,
|
||||
hook: 'beforeChange',
|
||||
operation: 'update',
|
||||
unflattenLocales: true,
|
||||
originalDoc,
|
||||
docWithLocales: globalJSON,
|
||||
});
|
||||
|
||||
await global.save();
|
||||
// /////////////////////////////////////
|
||||
// Update
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (locale && global.setLocale) {
|
||||
global.setLocale(locale, fallbackLocale);
|
||||
if (global) {
|
||||
global = await Model.findOneAndUpdate(
|
||||
{ globalType: slug },
|
||||
result,
|
||||
{ overwrite: true, new: true },
|
||||
);
|
||||
} else {
|
||||
global = await Model.create(result);
|
||||
}
|
||||
|
||||
global = global.toJSON({ virtuals: true });
|
||||
global = removeInternalFields(global);
|
||||
global = JSON.stringify(global);
|
||||
global = JSON.parse(global);
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 8. Execute field-level hooks and access
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
global = await this.performFieldOperations(globalConfig, {
|
||||
@@ -121,10 +146,11 @@ async function update(args) {
|
||||
req,
|
||||
depth,
|
||||
showHiddenFields,
|
||||
flattenLocales: true,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 9. Execute after global hook
|
||||
// afterRead - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
|
||||
@@ -137,11 +163,9 @@ async function update(args) {
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 10. Return global
|
||||
// Return results
|
||||
// /////////////////////////////////////
|
||||
|
||||
global = removeInternalFields(global);
|
||||
|
||||
return global;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
export default function formatRefPathLocales(schema, parentSchema?: any, parentPath?: string): void {
|
||||
// Loop through all refPaths within schema
|
||||
schema.eachPath((pathname, schemaType) => {
|
||||
// If a dynamic refPath is found
|
||||
if (schemaType.options.refPath && schemaType.options.refPath.includes('{{LOCALE}}') && parentSchema) {
|
||||
// Create a clone of the schema for each locale
|
||||
const newSchema = schema.clone();
|
||||
|
||||
// Remove the old pathname in order to rebuild it after it's formatted
|
||||
newSchema.remove(pathname);
|
||||
|
||||
// Get the locale from the parent path
|
||||
let locale = parentPath;
|
||||
|
||||
// Split the parent path and take only the last segment as locale
|
||||
if (parentPath && parentPath.includes('.')) {
|
||||
locale = parentPath.split('.').pop();
|
||||
}
|
||||
|
||||
// Replace {{LOCALE}} appropriately
|
||||
const refPath = schemaType.options.refPath.replace('{{LOCALE}}', locale);
|
||||
|
||||
// Add new schemaType back to newly cloned schema
|
||||
newSchema.add({
|
||||
[pathname]: {
|
||||
...schemaType.options,
|
||||
refPath,
|
||||
},
|
||||
});
|
||||
|
||||
// Removing and adding a path to a schema does not update tree, so do it manually
|
||||
newSchema.tree[pathname].refPath = refPath;
|
||||
|
||||
const parentSchemaType = parentSchema.path(parentPath).instance;
|
||||
|
||||
// Remove old schema from parent
|
||||
parentSchema.remove(parentPath);
|
||||
|
||||
// Replace newly cloned and updated schema on parent
|
||||
parentSchema.add({
|
||||
[parentPath]: parentSchemaType === 'Array' ? [newSchema] : newSchema,
|
||||
});
|
||||
}
|
||||
|
||||
// If nested schema found, continue recursively
|
||||
if (schemaType.schema) {
|
||||
formatRefPathLocales(schemaType.schema, schema, pathname);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
/* eslint-disable func-names */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
import mongoose from 'mongoose';
|
||||
import sanitizeFallbackLocale from './sanitizeFallbackLocale';
|
||||
import formatRefPathLocales from './formatRefPathLocales';
|
||||
|
||||
export default function localizationPlugin(schema: any, options): void {
|
||||
if (!options || !options.locales || !Array.isArray(options.locales) || !options.locales.length) {
|
||||
throw new mongoose.Error('Required locales array is missing');
|
||||
}
|
||||
|
||||
schema.eachPath((path, schemaType) => {
|
||||
if (schemaType.schema) { // propagate plugin initialization for sub-documents schemas
|
||||
schemaType.schema.plugin(localizationPlugin, options);
|
||||
}
|
||||
|
||||
if (!schemaType.options.localized && !(schemaType.schema && schemaType.schema.options.localized)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (schemaType.options.unique) {
|
||||
schemaType.options.sparse = true;
|
||||
}
|
||||
|
||||
const pathArray = path.split('.');
|
||||
const key = pathArray.pop();
|
||||
let prefix = pathArray.join('.');
|
||||
|
||||
if (prefix) prefix += '.';
|
||||
|
||||
// removing real path, it will be changed to virtual later
|
||||
schema.remove(path);
|
||||
|
||||
// schema.remove removes path from paths object only, but doesn't update tree
|
||||
// sounds like a bug, removing item from the tree manually
|
||||
const tree = pathArray.reduce((mem, part) => mem[part], schema.tree);
|
||||
delete tree[key];
|
||||
|
||||
schema.virtual(path)
|
||||
.get(function () {
|
||||
// embedded and sub-documents will use locale methods from the top level document
|
||||
const owner = this.ownerDocument ? this.ownerDocument() : this;
|
||||
const locale = owner.getLocale();
|
||||
const localeSubDoc = this.$__getValue(path);
|
||||
|
||||
if (localeSubDoc === null || localeSubDoc === undefined) {
|
||||
return localeSubDoc;
|
||||
}
|
||||
|
||||
const value = localeSubDoc[locale] || null;
|
||||
|
||||
if (locale === 'all') {
|
||||
return localeSubDoc;
|
||||
}
|
||||
|
||||
// If there is no value to return, AKA no translation in locale, handle fallbacks
|
||||
if (!value) {
|
||||
// If user specified fallback code as null, send back null
|
||||
if (this.fallbackLocale === null || (this.fallbackLocale && !localeSubDoc[this.fallbackLocale])) {
|
||||
return null;
|
||||
|
||||
// If user specified fallback code AND record exists, return that
|
||||
} if (localeSubDoc[this.fallbackLocale]) {
|
||||
return localeSubDoc[this.fallbackLocale];
|
||||
|
||||
// Otherwise, check if there is a default fallback value and if so, send that
|
||||
} if (options.fallback && localeSubDoc[options.defaultLocale]) {
|
||||
return localeSubDoc[options.defaultLocale];
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
})
|
||||
.set(function (value) {
|
||||
// embedded and sub-documents will use locale methods from the top level document
|
||||
const owner = this.ownerDocument ? this.ownerDocument() : this;
|
||||
const locale = owner.getLocale();
|
||||
|
||||
this.set(`${path}.${locale}`, value);
|
||||
});
|
||||
|
||||
// localized option is not needed for the current path any more,
|
||||
// and is unwanted for all child locale-properties
|
||||
// delete schemaType.options.localized; // This was removed to allow viewing inside query parser
|
||||
|
||||
const localizedObject = {
|
||||
[key]: {},
|
||||
};
|
||||
|
||||
options.locales.forEach(function (locale) {
|
||||
const localeOptions = { ...schemaType.options };
|
||||
if (locale !== options.defaultLocale) {
|
||||
delete localeOptions.default;
|
||||
delete localeOptions.required;
|
||||
}
|
||||
|
||||
if (schemaType.options.defaultAll) {
|
||||
localeOptions.default = schemaType.options.defaultAll;
|
||||
}
|
||||
|
||||
if (schemaType.options.requiredAll) {
|
||||
localeOptions.required = schemaType.options.requiredAll;
|
||||
}
|
||||
|
||||
this[locale] = localeOptions;
|
||||
}, localizedObject[key]);
|
||||
|
||||
schema.add(localizedObject, prefix);
|
||||
});
|
||||
|
||||
schema.eachPath((path, schemaType) => {
|
||||
if (schemaType.schema && schemaType.options.localized && schemaType.schema.discriminators) {
|
||||
Object.keys(schemaType.schema.discriminators).forEach((key) => {
|
||||
if (schema.path(path)) {
|
||||
schema.path(path).discriminator(key, schemaType.schema.discriminators[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// document methods to set the locale for each model instance (document)
|
||||
schema.method({
|
||||
getLocales() {
|
||||
return options.locales;
|
||||
},
|
||||
getLocale() {
|
||||
return this.docLocale || options.defaultLocale;
|
||||
},
|
||||
setLocale(locale, fallbackLocale) {
|
||||
const locales = [...this.getLocales(), 'all'];
|
||||
if (locale && locales.indexOf(locale) !== -1) {
|
||||
this.docLocale = locale;
|
||||
}
|
||||
|
||||
this.fallbackLocale = sanitizeFallbackLocale(fallbackLocale);
|
||||
this.schema.eachPath((path, schemaType) => {
|
||||
if (schemaType.options.type instanceof Array) {
|
||||
if (this[path]) this[path].forEach((doc) => doc.setLocale && doc.setLocale(locale, this.fallbackLocale));
|
||||
}
|
||||
|
||||
if (schemaType.options.ref && this[path]) {
|
||||
if (this[path] && this[path].setLocale) this[path].setLocale(locale, this.fallbackLocale);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Find any dynamic {{LOCALE}} in refPaths and modify schemas appropriately
|
||||
formatRefPathLocales(schema);
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { Schema, SchemaDefinition } from 'mongoose';
|
||||
import { Config } from '../config/types';
|
||||
import { MissingFieldInputOptions } from '../errors';
|
||||
import { ArrayField, Block, BlockField, Field, GroupField, RadioField, RelationshipField, RowField, SelectField, UploadField } from '../fields/config/types';
|
||||
import localizationPlugin from '../localization/plugin';
|
||||
|
||||
type FieldSchemaGenerator = (field: Field, fields: SchemaDefinition, config: Config) => SchemaDefinition;
|
||||
|
||||
@@ -23,33 +21,30 @@ const setBlockDiscriminators = (fields: Field[], schema: Schema, config: Config)
|
||||
|
||||
const blockSchema = new Schema(blockSchemaFields, { _id: false });
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Possible incorrect typing in mongoose types, this works
|
||||
schema.path(field.name).discriminator(blockItem.slug, blockSchema);
|
||||
|
||||
if (config.localization) {
|
||||
blockSchema.plugin(localizationPlugin, config.localization);
|
||||
if (field.localized) {
|
||||
config.localization.locales.forEach((locale) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Possible incorrect typing in mongoose types, this works
|
||||
schema.path(`${field.name}.${locale}`).discriminator(blockItem.slug, blockSchema);
|
||||
setBlockDiscriminators(blockItem.fields, blockSchema, config);
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Possible incorrect typing in mongoose types, this works
|
||||
schema.path(field.name).discriminator(blockItem.slug, blockSchema);
|
||||
setBlockDiscriminators(blockItem.fields, blockSchema, config);
|
||||
}
|
||||
|
||||
setBlockDiscriminators(blockItem.fields, blockSchema, config);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const formatBaseSchema = (field: Field) => {
|
||||
const createAccess = field.access && field.access.create;
|
||||
|
||||
const condition = field.admin && field.admin.condition;
|
||||
|
||||
return {
|
||||
localized: field.localized || false,
|
||||
unique: field.unique || false,
|
||||
required: (field.required && !field.localized && !condition && !createAccess) || false,
|
||||
default: field.defaultValue || undefined,
|
||||
index: field.index || field.unique || false,
|
||||
};
|
||||
};
|
||||
const formatBaseSchema = (field: Field) => ({
|
||||
unique: field.unique || false,
|
||||
required: (field.required && !field.localized && !field?.admin?.condition && !field?.access?.create) || false,
|
||||
default: field.defaultValue || undefined,
|
||||
index: field.index || field.unique || false,
|
||||
});
|
||||
|
||||
const buildSchema = (config: Config, configFields: Field[], options = {}): Schema => {
|
||||
let fields = {};
|
||||
@@ -70,36 +65,134 @@ const buildSchema = (config: Config, configFields: Field[], options = {}): Schem
|
||||
};
|
||||
|
||||
const fieldToSchemaMap = {
|
||||
number: (field: Field, fields: SchemaDefinition): SchemaDefinition => ({
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: Number },
|
||||
}),
|
||||
text: (field: Field, fields: SchemaDefinition): SchemaDefinition => ({
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: String },
|
||||
}),
|
||||
email: (field: Field, fields: SchemaDefinition): SchemaDefinition => ({
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: String },
|
||||
}),
|
||||
textarea: (field: Field, fields: SchemaDefinition): SchemaDefinition => ({
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: String },
|
||||
}),
|
||||
richText: (field: Field, fields: SchemaDefinition): SchemaDefinition => ({
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: Schema.Types.Mixed },
|
||||
}),
|
||||
code: (field: Field, fields: SchemaDefinition): SchemaDefinition => ({
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: String },
|
||||
}),
|
||||
radio: (field: RadioField, fields: SchemaDefinition) => {
|
||||
if (!field.options || field.options.length === 0) {
|
||||
throw new MissingFieldInputOptions(field);
|
||||
number: (field: Field, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const baseSchema = { ...formatBaseSchema(field), type: Number };
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
const schema = {
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
text: (field: Field, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const baseSchema = { ...formatBaseSchema(field), type: String };
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
email: (field: Field, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const baseSchema = { ...formatBaseSchema(field), type: String };
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
textarea: (field: Field, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const baseSchema = { ...formatBaseSchema(field), type: String };
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
richText: (field: Field, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const baseSchema = { ...formatBaseSchema(field), type: Schema.Types.Mixed };
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
code: (field: Field, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const baseSchema = { ...formatBaseSchema(field), type: String };
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
radio: (field: RadioField, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field),
|
||||
type: String,
|
||||
enum: field.options.map((option) => {
|
||||
@@ -107,57 +200,146 @@ const fieldToSchemaMap = {
|
||||
return option;
|
||||
}),
|
||||
};
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schema,
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
checkbox: (field: Field, fields: SchemaDefinition): SchemaDefinition => ({
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: Boolean },
|
||||
}),
|
||||
date: (field: Field, fields: SchemaDefinition): SchemaDefinition => ({
|
||||
...fields,
|
||||
[field.name]: { ...formatBaseSchema(field), type: Date },
|
||||
}),
|
||||
upload: (field: UploadField, fields: SchemaDefinition): SchemaDefinition => ({
|
||||
...fields,
|
||||
[field.name]: {
|
||||
checkbox: (field: Field, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const baseSchema = { ...formatBaseSchema(field), type: Boolean };
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
date: (field: Field, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const baseSchema = { ...formatBaseSchema(field), type: Date };
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
upload: (field: UploadField, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field),
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: field.relationTo,
|
||||
},
|
||||
}),
|
||||
relationship: (field: RelationshipField, fields: SchemaDefinition) => {
|
||||
let schema: { [key: string]: any } = {};
|
||||
};
|
||||
|
||||
if (Array.isArray(field.relationTo)) {
|
||||
schema._id = false;
|
||||
schema.value = {
|
||||
type: Schema.Types.ObjectId,
|
||||
refPath: `${field.name}${field.localized ? '.{{LOCALE}}' : ''}.relationTo`,
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
schema.relationTo = { type: String, enum: field.relationTo };
|
||||
} else {
|
||||
schema = {
|
||||
...formatBaseSchema(field),
|
||||
};
|
||||
|
||||
schema.type = Schema.Types.ObjectId;
|
||||
schema.ref = field.relationTo;
|
||||
}
|
||||
|
||||
if (field.hasMany) {
|
||||
schema = {
|
||||
type: [schema],
|
||||
localized: field.localized || false,
|
||||
};
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schema,
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
relationship: (field: RelationshipField, fields: SchemaDefinition, config: Config) => {
|
||||
const hasManyRelations = Array.isArray(field.relationTo);
|
||||
let schemaToReturn: { [key: string]: any } = {};
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((locales, locale) => {
|
||||
let localeSchema: { [key: string]: any } = {};
|
||||
|
||||
if (hasManyRelations) {
|
||||
localeSchema._id = false;
|
||||
localeSchema.value = {
|
||||
type: Schema.Types.ObjectId,
|
||||
refPath: `${field.name}.${locale}.relationTo`,
|
||||
};
|
||||
localeSchema.relationTo = { type: String, enum: field.relationTo };
|
||||
} else {
|
||||
localeSchema = {
|
||||
...formatBaseSchema(field),
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: field.relationTo,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...locales,
|
||||
[locale]: field.hasMany ? [localeSchema] : localeSchema,
|
||||
};
|
||||
}, {}),
|
||||
localized: true,
|
||||
};
|
||||
} else if (hasManyRelations) {
|
||||
schemaToReturn._id = false;
|
||||
schemaToReturn.value = {
|
||||
type: Schema.Types.ObjectId,
|
||||
refPath: `${field.name}.relationTo`,
|
||||
};
|
||||
schemaToReturn.relationTo = { type: String, enum: field.relationTo };
|
||||
|
||||
if (field.hasMany) schemaToReturn = [schemaToReturn];
|
||||
} else {
|
||||
schemaToReturn = {
|
||||
...formatBaseSchema(field),
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: field.relationTo,
|
||||
};
|
||||
|
||||
if (field.hasMany) schemaToReturn = [schemaToReturn];
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
row: (field: RowField, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
@@ -175,34 +357,58 @@ const fieldToSchemaMap = {
|
||||
return newFields;
|
||||
},
|
||||
array: (field: ArrayField, fields: SchemaDefinition, config: Config) => {
|
||||
const schema = buildSchema(config, field.fields, { _id: false, id: false });
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field),
|
||||
type: [buildSchema(config, field.fields, { _id: false, id: false })],
|
||||
};
|
||||
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: {
|
||||
...formatBaseSchema(field),
|
||||
type: [schema],
|
||||
},
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
group: (field: GroupField, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const schema = buildSchema(config, field.fields, { _id: false, id: false });
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field),
|
||||
required: field.fields.some((subField) => subField.required === true),
|
||||
type: buildSchema(config, field.fields, { _id: false, id: false }),
|
||||
};
|
||||
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: {
|
||||
...formatBaseSchema(field),
|
||||
required: field.fields.some((subField) => subField.required === true),
|
||||
type: schema,
|
||||
},
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
select: (field: SelectField, fields: SchemaDefinition) => {
|
||||
if (!field.options || field.options.length === 0) {
|
||||
throw new MissingFieldInputOptions(field);
|
||||
}
|
||||
|
||||
const schema = {
|
||||
select: (field: SelectField, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field),
|
||||
type: String,
|
||||
enum: field.options.map((option) => {
|
||||
@@ -211,20 +417,43 @@ const fieldToSchemaMap = {
|
||||
}),
|
||||
};
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: field.hasMany ? [schema] : schema,
|
||||
};
|
||||
},
|
||||
blocks: (field: BlockField, fields: SchemaDefinition) => {
|
||||
const blocksSchema = new Schema({ blockName: String }, { discriminatorKey: 'blockType', _id: false, id: false });
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = {
|
||||
type: config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {}),
|
||||
localized: true,
|
||||
};
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
if (field.hasMany) schemaToReturn = [schemaToReturn];
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: {
|
||||
type: [blocksSchema],
|
||||
localized: field.localized || false,
|
||||
},
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
blocks: (field: BlockField, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const baseSchema = [new Schema({ blockName: String }, { discriminatorKey: 'blockType', _id: false, id: false })];
|
||||
let schemaToReturn;
|
||||
|
||||
if (field.localized) {
|
||||
schemaToReturn = config.localization.locales.reduce((localeSchema, locale) => ({
|
||||
...localeSchema,
|
||||
[locale]: baseSchema,
|
||||
}), {});
|
||||
} else {
|
||||
schemaToReturn = baseSchema;
|
||||
}
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: schemaToReturn,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ const connectMongoose = async (url: string, options: ConnectionOptions): Promise
|
||||
useUnifiedTopology: true,
|
||||
useCreateIndex: true,
|
||||
autoIndex: false,
|
||||
useFindAndModify: false,
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
|
||||
Reference in New Issue
Block a user