@websolutespa/payload-plugin-bowl
Version:
Bowl PayloadCms plugin of the BOM Repository
457 lines (455 loc) • 17.8 kB
JavaScript
import { isNonEmptyArray } from '@websolutespa/bom-core';
import { HttpStatus, isDataField, parseQueryDepth, parseQueryInt } from '@websolutespa/payload-utils';
import { bulkWrite, getRequestCollection, ResponseBadRequest, ResponseError, ResponseSuccess, ResponseUnprocessable } from '@websolutespa/payload-utils/server';
import * as Papa from 'papaparse';
import { addDataAndFileToRequest } from 'payload';
import { options } from '../../options';
import { decorateRichText_ } from '../decorators/rich-text';
import { decorateSchema_ } from '../decorators/schema';
import { findByIDHandler } from '../utils/findByIDHandler';
import { findHandler } from '../utils/findHandler';
import { ImportMode } from './types';
import { setMixerContext } from './utils';
export function parseDepth(depth, defaultDepth = 1) {
if (depth !== undefined) {
return parseInt(String(depth), 10);
} else {
return defaultDepth;
}
}
export async function getCollectionItems(req, slug, depth) {
const { query = {}, payload, user } = req;
const { locale, where, sort, draft } = query;
depth = depth !== undefined ? depth : parseQueryDepth(query.depth);
query.depth = String(depth);
const limit = parseQueryInt(query.limit, 10000);
/*
console.log('getCollectionItems',
'slug', slug, 'locale',
locale, 'where',
where, 'sort',
sort, 'draft',
draft, 'depth',
depth, 'user',
user?.email
);
*/ try {
const response = await payload.find({
collection: slug,
draft: draft === 'true',
locale: locale,
fallbackLocale: options.defaultLocale,
where: where,
sort: sort,
depth,
limit,
pagination: false,
req,
user,
overrideAccess: false
});
// console.log('getCollectionItems', slug, user?.role);
const items = response.docs ? response.docs : [];
// console.log('getCollectionItems.items.length', items.length);
return items;
} catch (error) {
// 403 is expected, it means the collection is private
if (error.status !== 403) {
throw error;
}
return [];
}
}
export async function getCollectionItem(req, slug, id, depth) {
const { query = {}, payload, user } = req;
const { locale, draft } = query;
depth = depth !== undefined ? depth : parseQueryDepth(query.depth);
query.depth = String(depth);
// console.log('getCollectionItem', 'slug', slug, 'id', id, 'locale', locale, 'draft', draft, 'depth', depth, 'user', user?.email);
const item = await payload.findByID({
collection: slug,
id,
draft: draft === 'true',
locale: locale,
fallbackLocale: options.defaultLocale,
depth,
req,
user,
overrideAccess: false
});
return item;
}
export async function getGlobalItems(req, slug, depth) {
const { query = {}, payload, user } = req;
const { locale, draft } = query;
depth = depth !== undefined ? depth : parseQueryDepth(query.depth);
query.depth = String(depth);
// console.log('getGlobalItems', 'slug', slug, 'locale', locale, 'draft', draft, 'depth', depth, 'user', user?.email);
try {
const response = await payload.findGlobal({
slug,
depth,
locale: locale,
fallbackLocale: options.defaultLocale,
showHiddenFields: false,
draft: draft === 'true',
req,
user,
overrideAccess: false
});
const items = response.items ? response.items : [];
return items;
} catch (error) {
// 403 is expected, it means the collection is private
if (error.status !== 403) {
throw error;
}
return [];
}
}
/**
* Rest api collection index get handler.
*/ export const collectionIndexGet = (slug)=>({
path: '/',
method: 'get',
handler: async (req)=>{
try {
const { query } = req;
if (!query) {
return findHandler(req);
}
// console.log('collectionIndexGet', ...Object.entries(query));
const { locale, market, pagination } = query;
if (typeof market === 'string' && typeof locale === 'string') {
// const context = await setMixerContext(req, market, locale);
// console.log('collectionIndexGet.context', context.market, context.locale, context.routes.length, context.categories.length);
if (pagination === 'true') {
return findHandler(req);
}
const items = await getCollectionItems(req, slug);
return ResponseSuccess(items);
} else if (pagination === 'false') {
const items = await getCollectionItems(req, slug);
return ResponseSuccess(items);
}
return findHandler(req);
} catch (error) {
console.error(`CollectionService.collectionIndexGet.${slug}.error`, error);
return ResponseError(error);
}
}
});
/**
* Rest api collection detail get handler.
*/ export const collectionDetailGet = (slug)=>({
path: '/:id',
method: 'get',
handler: async (req)=>{
// console.log('collectionDetailGet', slug, ...Object.entries(req.query));
try {
const { query } = req;
if (!query) {
return findByIDHandler(req);
}
// console.log('collectionDetailGet', ...Object.entries(query));
const { market, locale } = query;
if (!(typeof market === 'string' && typeof locale === 'string')) {
return findByIDHandler(req);
}
// const context = await setMixerContext(req, market, locale);
// console.log('collectionDetailGet.context', context.market, context.locale, context.routes.length, context.categories.length);
return findByIDHandler(req);
} catch (error) {
console.error(`CollectionService.collectionDetailGet.${slug}.error`, error);
return ResponseError(error);
}
}
});
/**
* Rest api collection bulk patch handler.
*/ export const collectionBulkPatch = (slug)=>({
path: '/bulk',
method: 'patch',
handler: async (req)=>{
/*
if (!hasRole(req.user, roles.Admin, roles.Editor)) {
return next();
}
*/ await addDataAndFileToRequest(req);
const { payload, data } = req;
if (!isNonEmptyArray(data)) {
return ResponseBadRequest();
}
const records = data;
try {
const bulkWriteResult = await bulkWrite(payload, slug, records);
if (bulkWriteResult === undefined) {
return ResponseUnprocessable();
}
// console.log('CollectionService.collectionBulkPatch.success', bulkWriteResult);
const items = await getCollectionItems(req, slug);
return ResponseSuccess(items);
} catch (error) {
console.error(`CollectionService.collectionBulkPatch.${slug}.error`, error);
return ResponseError(error);
}
}
});
/**
* Rest api collection update patch handler.
* known issue: collectionUpdatePatch() does not work, needs to be fixed
*/ export const collectionUpdatePatch = (slug)=>({
path: '/update',
method: 'patch',
handler: async (req)=>{
const collection = getRequestCollection(req);
/*
if (!hasRole(req.user, roles.Admin, roles.Editor)) {
return next();
}
*/ const { payload } = req;
const adapter = payload.db;
const model = adapter.collections[slug];
if (!model) {
return ResponseUnprocessable();
}
await addDataAndFileToRequest(req);
if (!isNonEmptyArray(req.data)) {
return ResponseUnprocessable();
}
try {
const records = req.data;
const keyMap = {};
records.forEach((x)=>Object.keys(x).forEach((k)=>keyMap[k] = k));
const keys = Object.keys(keyMap);
const fields = collection.config.fields.filter((x)=>isDataField(x));
const filteredKeys = fields.map((x)=>x.name).filter((name)=>keys.includes(name));
const $set = {};
filteredKeys.forEach((key)=>{
if (key !== 'id') {
$set[key] = {
$switch: {
// branches: records.filter(x => x.hasOwnProperty('order')).map(x => (
branches: records.map((x)=>({
case: {
$eq: [
'$_id',
x.id
]
},
then: x[key]
}))
}
};
}
});
// const sourceItems = await getCollectionItems(req, slug);
const where = {
_id: {
$in: records.map((x)=>x.id)
}
};
const update = [
{
$set
}
];
// console.log('where', where);
// console.log('update', update);
const response = await model.updateMany(where, update, {
multi: true
});
// console.log('response', response);
/*
response {
acknowledged: true,
modifiedCount: 33,
upsertedId: null,
upsertedCount: 0,
matchedCount: 33
}
*/ /*
const promises = records.map(item => model.findByIdAndUpdate({ _id: item.id }, item, { new: true }).lean());
const responses = await Promise.all(promises);
*/ const items = await getCollectionItems(req, slug);
return ResponseSuccess(items);
} catch (error) {
console.error(`CollectionService.collectionUpdatePatch.${slug}.error`, error);
return ResponseError(error);
}
}
});
/**
* Rest api collection export get handler.
*/ export const collectionExportGet = (slug)=>({
path: '/export',
method: 'get',
handler: async (req)=>{
/*
if (!hasRole(req.user, roles.Admin, roles.Editor)) {
return next();
}
*/ try {
const items = await getCollectionItems(req, slug);
const csv = Papa.unparse(items);
return new Response(csv, {
headers: new Headers({
'Content-Type': 'text/csv',
'Content-Disposition': `attachment; filename="${slug}.csv"`
}),
status: 200
});
} catch (error) {
console.error(`CollectionService.collectionExportGet.${slug}.error`, error);
return ResponseError(error);
}
}
});
/**
* Rest api collection import post handler.
*/ export const collectionImportPost = (slug)=>({
path: '/import',
method: 'post',
handler: async (req)=>{
/*
if (!hasRole(req.user, roles.Admin, roles.Editor)) {
return next();
}
*/ const { query, payload, user } = req;
// console.log('collectionImportPost', 'query', query, 'user', user);
const { locale, where, sort, depth, draft, page, limit, pagination, mode } = query;
const importMode = mode || ImportMode.Append;
const defaultLocale = payload.config.localization ? payload.config.localization.defaultLocale : 'en';
// console.log('locale', locale, 'defaultLocale', defaultLocale);
try {
await addDataAndFileToRequest(req);
if (!isNonEmptyArray(req.data?.items)) {
return ResponseUnprocessable();
}
const deleteItems = async ()=>{
return await payload.delete({
collection: slug,
where: {
id: {
exists: true
}
},
id: undefined,
depth: 0,
locale: locale,
fallbackLocale: defaultLocale,
user,
showHiddenFields: true,
overrideAccess: true
});
};
const insertItem = async (item)=>{
return await payload.create({
collection: slug,
data: item,
locale: locale,
fallbackLocale: defaultLocale,
user,
showHiddenFields: false,
overrideAccess: true
});
};
const updateItem = async (item)=>{
return await payload.update({
collection: slug,
data: item,
id: item.id,
depth: 0,
locale: locale,
fallbackLocale: defaultLocale,
user,
showHiddenFields: true,
overrideAccess: true
});
};
const items = req.data.items;
if (items && items.length) {
try {
if (importMode === ImportMode.Replace) {
await deleteItems();
}
for (const item of items){
if (importMode === ImportMode.Update) {
await updateItem(item);
} else {
await insertItem(item);
}
}
return ResponseSuccess(items);
} catch (error) {
console.error(`CollectionService.collectionImportPost.${slug}.error`, error);
return Response.json({
errors: [
{
message: error.message || error.name || error.statusText || 'An error occurred'
}
]
}, {
status: error.status || 500
});
}
} else {
return Response.json({
message: 'Bad Request'
}, {
status: HttpStatus.BAD_REQUEST
});
}
} catch (error) {
console.error(`CollectionService.collectionImportPost.${slug}.error`, error);
return ResponseError(error);
}
}
});
/**
* Decorate Mixer context.
*/ export const beforeOperationHook = async ({ // collection,
// context,
// operation,
args, req })=>{
if (typeof req.query.market === 'string' && typeof req.query.locale === 'string' && !req.context.mixer) {
// console.log('beforeReadHook.setMixerContext', req.query.market, req.query.locale);
req.context.mixer = true;
await setMixerContext(req, req.query.market, req.query.locale);
}
return args;
};
/**
* Decorate record with static collection data.
*/ export const afterCollectionReadHook = (collectionConfig)=>async ({ doc, req, context, findMany })=>{
const { query = {} } = req;
// console.log('afterCollectionReadHook.req', req);
// console.log('afterCollectionReadHook.query', query);
const { locale, market } = query;
// console.log('afterCollectionReadHook', locale, market, depth);
if (typeof locale === 'string' && typeof market === 'string') {
const withSchema = await decorateSchema_(doc, collectionConfig.slug);
const withRichText = await decorateRichText_(withSchema, collectionConfig.fields, context, req.payload.config);
return withRichText;
} else {
return doc;
}
}; /**
* !!! removed from v3
* Enforce unique field values during duplication.
export const beforeDuplicateCollectionHook: BeforeDuplicate = async ({
data,
collection,
}) => {
data = {
...data,
};
collection.fields.forEach(field => {
if (isDataField(field) && field.unique) {
data[field.name] = `${data[field.name]}-${uuid()}`;
}
});
return data;
};
*/
//# sourceMappingURL=collection.service.js.map