merge master

This commit is contained in:
Dan Ribbens
2020-04-22 15:12:33 -04:00
66 changed files with 337 additions and 380 deletions

View File

@@ -0,0 +1,27 @@
import React, {
createContext, useContext,
} from 'react';
import PropTypes from 'prop-types';
const Context = createContext({});
export const RowModifiedProvider = ({ children, lastModified }) => {
return (
<Context.Provider value={lastModified}>
{children}
</Context.Provider>
);
};
export const useRowModified = () => useContext(Context);
RowModifiedProvider.defaultProps = {
lastModified: null,
};
RowModifiedProvider.propTypes = {
children: PropTypes.node.isRequired,
lastModified: PropTypes.number,
};
export default Context;

View File

@@ -144,6 +144,24 @@ const Form = (props) => {
fields,
processing,
submitted,
countRows: (rowName) => {
const namePrefixToRemove = rowName.substring(0, rowName.lastIndexOf('.') + 1);
const rows = Object.keys(fields).reduce((matchedRows, key) => {
if (key.indexOf(`${rowName}.`) === 0) {
return {
...matchedRows,
[key.replace(namePrefixToRemove, '')]: fields[key],
};
}
return matchedRows;
}, {});
const unflattenedRows = unflatten(rows);
const rowCount = unflattenedRows[rowName.replace(namePrefixToRemove, '')]?.length || 0;
return rowCount;
},
}}
>
<HiddenInput

View File

@@ -80,10 +80,12 @@ function fieldReducer(state, action) {
// Add new object containing subfield names to unflattenedRows array
unflattenedRows.splice(rowIndex + 1, 0, subFields);
return {
const newState = {
...remainingFlattenedState,
...(flatten({ [name]: unflattenedRows }, { filters: flattenFilters })),
};
return newState;
}
case 'MOVE_ROW': {

View File

@@ -11,13 +11,22 @@ const RenderFields = ({
<>
{fieldSchema.map((field, i) => {
const { defaultValue } = field;
const FieldComponent = customComponents?.[field.name]?.field || fieldTypes[field.type];
let FieldComponent = field.hidden ? fieldTypes.hidden : fieldTypes[field.type];
if (customComponents?.[field.name]?.field) {
FieldComponent = customComponents[field.name].field;
}
// let FieldComponent = fieldTypes[field.type];
// if (field.hidden) {
// FieldComponent = fieldTypes.hidden;
// }
// if (customComponents?.[field.name]?.field) {
// FieldComponent = customComponents[field.name].field;
// }
if (FieldComponent) {
return (
<FieldComponent
fieldTypes={fieldTypes}
key={i}
key={field.name}
{...field}
validate={field.validate ? value => field.validate(value, field) : undefined}
defaultValue={initialData[field.name] || defaultValue}
@@ -56,7 +65,9 @@ RenderFields.propTypes = {
).isRequired,
initialData: PropTypes.shape({}),
customComponents: PropTypes.shape({}),
fieldTypes: PropTypes.shape({}).isRequired,
fieldTypes: PropTypes.shape({
hidden: PropTypes.node,
}).isRequired,
};
export default RenderFields;

View File

@@ -1,17 +1,18 @@
import React, {
useContext, useEffect, useReducer, useState, Fragment,
useContext, useEffect, useReducer, useState,
} from 'react';
import PropTypes from 'prop-types';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import { useModal } from '@trbl/react-modal';
import { RowModifiedProvider, useRowModified } from '../../Form/RowModified';
import withCondition from '../../withCondition';
import Button from '../../../controls/Button';
import FormContext from '../../Form/Context';
import Section from '../../../layout/Section';
import AddRowModal from './AddRowModal';
import collapsibleReducer from './reducer';
import DraggableSection from '../../DraggableSection'; // eslint-disable-line import/no-cycle
import DraggableSection from '../../DraggableSection';
import './index.scss';
@@ -27,15 +28,16 @@ const Flexible = (props) => {
fieldTypes,
} = props;
const parentRowsModified = useRowModified();
const { toggle: toggleModal, closeAll: closeAllModals } = useModal();
const [rowIndexBeingAdded, setRowIndexBeingAdded] = useState(null);
const [hasModifiedRows, setHasModifiedRows] = useState(false);
const [lastModified, setLastModified] = useState(null);
const [rowCount, setRowCount] = useState(0);
const [collapsibleStates, dispatchCollapsibleStates] = useReducer(collapsibleReducer, []);
const formContext = useContext(FormContext);
const modalSlug = `flexible-${name}`;
const { fields: fieldState, dispatchFields } = formContext;
const { fields: fieldState, dispatchFields, countRows } = formContext;
const addRow = (rowIndex, blockType) => {
const blockToAdd = blocks.find(block => block.slug === blockType);
@@ -49,7 +51,7 @@ const Flexible = (props) => {
});
setRowCount(rowCount + 1);
setHasModifiedRows(true);
setLastModified(Date.now());
};
const removeRow = (rowIndex) => {
@@ -63,7 +65,7 @@ const Flexible = (props) => {
});
setRowCount(rowCount - 1);
setHasModifiedRows(true);
setLastModified(Date.now());
};
const moveRow = (moveFromIndex, moveToIndex) => {
@@ -75,7 +77,7 @@ const Flexible = (props) => {
type: 'MOVE_COLLAPSIBLE', collapsibleIndex: moveFromIndex, moveToIndex,
});
setHasModifiedRows(true);
setLastModified(Date.now());
};
const openAddRowModal = (rowIndex) => {
@@ -90,9 +92,16 @@ const Flexible = (props) => {
moveRow(sourceIndex, destinationIndex);
};
const updateRowCountOnParentRowModified = () => {
const countedRows = countRows(name);
setRowCount(countedRows);
};
useEffect(updateRowCountOnParentRowModified, [parentRowsModified]);
useEffect(() => {
setRowCount(defaultValue.length);
setHasModifiedRows(false);
setLastModified(null);
dispatchCollapsibleStates({
type: 'SET_ALL_COLLAPSIBLES',
@@ -101,7 +110,7 @@ const Flexible = (props) => {
}, [defaultValue]);
return (
<Fragment>
<RowModifiedProvider lastModified={lastModified}>
<DragDropContext onDragEnd={onDragEnd}>
<div className={baseClass}>
<Section
@@ -117,7 +126,7 @@ const Flexible = (props) => {
{rowCount !== 0 && Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
let blockType = fieldState[`${name}.${rowIndex}.blockType`]?.value;
if (!hasModifiedRows && !blockType) {
if (!lastModified && !blockType) {
blockType = defaultValue?.[rowIndex]?.blockType;
}
@@ -137,14 +146,16 @@ const Flexible = (props) => {
...blockToRender.fields,
{
name: 'blockType',
type: 'hidden',
type: 'text',
hidden: true,
}, {
name: 'blockName',
type: 'hidden',
type: 'text',
hidden: true,
},
]}
singularLabel={blockType}
defaultValue={hasModifiedRows ? undefined : defaultValue[rowIndex]}
defaultValue={lastModified ? undefined : defaultValue[rowIndex]}
dispatchCollapsibleStates={dispatchCollapsibleStates}
collapsibleStates={collapsibleStates}
blockType="flexible"
@@ -178,7 +189,7 @@ const Flexible = (props) => {
slug={modalSlug}
blocks={blocks}
/>
</Fragment>
</RowModifiedProvider>
);
};

View File

@@ -4,11 +4,12 @@ import React, {
import PropTypes from 'prop-types';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import { RowModifiedProvider, useRowModified } from '../../Form/RowModified';
import withCondition from '../../withCondition';
import Button from '../../../controls/Button';
import FormContext from '../../Form/Context';
import Section from '../../../layout/Section';
import DraggableSection from '../../DraggableSection'; // eslint-disable-line import/no-cycle
import DraggableSection from '../../DraggableSection';
import collapsibleReducer from './reducer';
import './index.scss';
@@ -16,11 +17,12 @@ import './index.scss';
const baseClass = 'field-type repeater';
const Repeater = (props) => {
const parentRowsModified = useRowModified();
const [collapsibleStates, dispatchCollapsibleStates] = useReducer(collapsibleReducer, []);
const formContext = useContext(FormContext);
const [rowCount, setRowCount] = useState(0);
const [hasModifiedRows, setHasModifiedRows] = useState(false);
const { fields: fieldState, dispatchFields } = formContext;
const [lastModified, setLastModified] = useState(null);
const { fields: fieldState, dispatchFields, countRows } = formContext;
const {
label,
@@ -41,7 +43,7 @@ const Repeater = (props) => {
});
setRowCount(rowCount + 1);
setHasModifiedRows(true);
setLastModified(Date.now());
};
const removeRow = (rowIndex) => {
@@ -55,7 +57,7 @@ const Repeater = (props) => {
});
setRowCount(rowCount - 1);
setHasModifiedRows(true);
setLastModified(Date.now());
};
const moveRow = (moveFromIndex, moveToIndex) => {
@@ -67,11 +69,12 @@ const Repeater = (props) => {
type: 'MOVE_COLLAPSIBLE', collapsibleIndex: moveFromIndex, moveToIndex,
});
setHasModifiedRows(true);
setLastModified(Date.now());
};
useEffect(() => {
setRowCount(defaultValue.length);
setLastModified(null);
dispatchCollapsibleStates({
type: 'SET_ALL_COLLAPSIBLES',
@@ -79,6 +82,13 @@ const Repeater = (props) => {
});
}, [defaultValue]);
const updateRowCountOnParentRowModified = () => {
const countedRows = countRows(name);
setRowCount(countedRows);
};
useEffect(updateRowCountOnParentRowModified, [parentRowsModified]);
const onDragEnd = (result) => {
if (!result.destination) return;
const sourceIndex = result.source.index;
@@ -87,54 +97,56 @@ const Repeater = (props) => {
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<div className={baseClass}>
<Section heading={label}>
<>
<Droppable droppableId="repeater-drop">
{provided => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
<RowModifiedProvider lastModified={lastModified}>
<DragDropContext onDragEnd={onDragEnd}>
<div className={baseClass}>
<Section heading={label}>
<>
<Droppable droppableId="repeater-drop">
{provided => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
>
{rowCount !== 0
&& Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
return (
<DraggableSection
fieldTypes={fieldTypes}
key={rowIndex}
parentName={name}
singularLabel={singularLabel}
addRow={() => addRow(rowIndex)}
removeRow={() => removeRow(rowIndex)}
rowIndex={rowIndex}
fieldState={fieldState}
fieldSchema={fields}
defaultValue={lastModified ? undefined : defaultValue[rowIndex]}
dispatchCollapsibleStates={dispatchCollapsibleStates}
collapsibleStates={collapsibleStates}
/>
);
})
}
{provided.placeholder}
</div>
)}
</Droppable>
<div className={`${baseClass}__add-button-wrap`}>
<Button
onClick={() => addRow(rowCount)}
type="secondary"
>
{rowCount !== 0
&& Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
return (
<DraggableSection
fieldTypes={fieldTypes}
key={rowIndex}
parentName={name}
singularLabel={singularLabel}
addRow={() => addRow(rowIndex)}
removeRow={() => removeRow(rowIndex)}
rowIndex={rowIndex}
fieldState={fieldState}
fieldSchema={fields}
defaultValue={hasModifiedRows ? undefined : defaultValue[rowIndex]}
dispatchCollapsibleStates={dispatchCollapsibleStates}
collapsibleStates={collapsibleStates}
/>
);
})
}
{provided.placeholder}
</div>
)}
</Droppable>
{`Add ${singularLabel}`}
</Button>
</div>
</>
</Section>
<div className={`${baseClass}__add-button-wrap`}>
<Button
onClick={() => addRow(rowCount)}
type="secondary"
>
{`Add ${singularLabel}`}
</Button>
</div>
</>
</Section>
</div>
</DragDropContext>
</div>
</DragDropContext>
</RowModifiedProvider>
);
};

View File

@@ -2,21 +2,17 @@
const { create } = require('../../operations');
const createResolver = collection => async (_, args, context) => {
if (args.locale) {
context.locale = args.locale;
}
const options = {
config: collection.config,
Model: collection.Model,
data: args.data,
user: context.user,
api: 'GraphQL',
locale: context.locale,
fallbackLocale: context.fallbackLocale,
req: context,
};
if (args.locale) {
context.locale = args.locale;
options.locale = args.locale;
}
const result = await create(options);
return result;

View File

@@ -2,26 +2,16 @@
const { deleteQuery } = require('../../operations');
const deleteResolver = collection => async (_, args, context) => {
if (args.locale) context.locale = args.locale;
if (args.fallbackLocale) context.fallbackLocale = args.fallbackLocale;
const options = {
config: collection.config,
Model: collection.Model,
id: args.id,
user: context.user,
api: 'GraphQL',
locale: context.locale,
fallbackLocale: context.fallbackLocale,
req: context,
};
if (args.locale) {
context.locale = args.locale;
options.locale = args.locale;
}
if (args.fallbackLocale) {
context.fallbackLocale = args.fallbackLocale;
options.fallbackLocale = args.fallbackLocale;
}
const result = await deleteQuery(options);
return result;

View File

@@ -2,6 +2,9 @@
const { find } = require('../../operations');
const findResolver = collection => async (_, args, context) => {
if (args.locale) context.locale = args.locale;
if (args.fallbackLocale) context.fallbackLocale = args.fallbackLocale;
const options = {
config: collection.config,
Model: collection.Model,
@@ -10,22 +13,9 @@ const findResolver = collection => async (_, args, context) => {
limit: args.limit,
page: args.page,
sort: args.sort,
user: context.user,
api: 'GraphQL',
locale: context.locale,
fallbackLocale: context.fallbackLocale,
req: context,
};
if (args.locale) {
context.locale = args.locale;
options.locale = args.locale;
}
if (args.fallbackLocale) {
context.fallbackLocale = args.fallbackLocale;
options.fallbackLocale = args.fallbackLocale;
}
const results = await find(options);
return results;

View File

@@ -2,27 +2,17 @@
const { findByID } = require('../../operations');
const findByIDResolver = collection => async (_, args, context) => {
if (args.locale) context.locale = args.locale;
if (args.fallbackLocale) context.fallbackLocale = args.fallbackLocale;
const options = {
config: collection.config,
Model: collection.Model,
depth: 0,
id: args.id,
user: context.user,
api: 'GraphQL',
locale: context.locale,
fallbackLocale: context.fallbackLocale,
req: context,
};
if (args.locale) {
context.locale = args.locale;
options.locale = args.locale;
}
if (args.fallbackLocale) {
context.fallbackLocale = args.fallbackLocale;
options.fallbackLocale = args.fallbackLocale;
}
const result = await findByID(options);
return result;

View File

@@ -2,28 +2,18 @@
const { update } = require('../../operations');
const updateResolver = collection => async (_, args, context) => {
if (args.locale) context.locale = args.locale;
if (args.fallbackLocale) context.fallbackLocale = args.fallbackLocale;
const options = {
config: collection.config,
Model: collection.Model,
locale: context.locale,
fallbackLocale: context.fallbackLocale,
data: args.data,
id: args.id,
user: context.user,
api: 'GraphQL',
depth: 0,
req: context,
};
if (args.locale) {
context.locale = args.locale;
options.locale = args.locale;
}
if (args.fallbackLocale) {
context.fallbackLocale = args.fallbackLocale;
options.fallbackLocale = args.fallbackLocale;
}
const result = await update(options);
return result;

View File

@@ -10,15 +10,7 @@ const create = async (args) => {
await executePolicy(args, args.config.policies.create);
let options = {
Model: args.Model,
config: args.config,
locale: args.locale,
fallbackLocale: args.fallbackLocale,
user: args.user,
api: args.api,
data: args.data,
};
let options = { ...args };
// /////////////////////////////////////
// 2. Validate incoming data
@@ -48,9 +40,11 @@ const create = async (args) => {
const {
Model,
locale,
fallbackLocale,
data,
req: {
locale,
fallbackLocale,
},
} = options;
let result = new Model();

View File

@@ -9,15 +9,7 @@ const deleteQuery = async (args) => {
await executePolicy(args, args.config.policies.delete);
let options = {
id: args.id,
Model: args.Model,
config: args.config,
locale: args.locale,
fallbackLocale: args.fallbackLocale,
user: args.user,
api: args.api,
};
let options = { ...args };
// /////////////////////////////////////
// 2. Execute before collection hook
@@ -36,8 +28,10 @@ const deleteQuery = async (args) => {
const {
Model,
id,
locale,
fallbackLocale,
req: {
locale,
fallbackLocale,
},
} = options;
let result = await Model.findOneAndDelete({ _id: id });

View File

@@ -13,17 +13,8 @@ const find = async (args) => {
if (args.where) queryToBuild.where = args.where;
let options = {
...args,
query: await args.Model.buildQuery(queryToBuild, args.locale),
Model: args.Model,
locale: args.locale,
fallbackLocale: args.fallbackLocale,
page: args.page,
limit: args.limit,
sort: args.sort,
depth: args.depth,
config: args.config,
user: args.user,
api: args.api,
};
// /////////////////////////////////////
@@ -45,11 +36,13 @@ const find = async (args) => {
page,
limit,
sort,
api,
depth,
Model,
locale,
fallbackLocale,
req: {
locale,
fallbackLocale,
payloadAPI,
},
} = options;
const optionsToExecute = {
@@ -62,7 +55,7 @@ const find = async (args) => {
// Only allow depth override within REST.
// If allowed in GraphQL, it would break resolvers
// as a full object will be returned instead of an ID string
if (api === 'REST') {
if (payloadAPI === 'REST') {
if (depth && depth !== '0') {
optionsToExecute.options.autopopulate = {
maxDepth: parseInt(depth, 10),

View File

@@ -12,14 +12,8 @@ const findByID = async (args) => {
await executePolicy(args, policy);
let options = {
...args,
query: { _id: args.id },
Model: args.Model,
locale: args.locale,
fallbackLocale: args.fallbackLocale,
depth: args.depth,
config: args.config,
user: args.user,
api: args.api,
};
// /////////////////////////////////////
@@ -38,11 +32,13 @@ const findByID = async (args) => {
const {
depth,
api,
Model,
query,
locale,
fallbackLocale,
req: {
locale,
fallbackLocale,
payloadAPI,
},
} = options;
const queryOptionsToExecute = {
@@ -52,7 +48,7 @@ const findByID = async (args) => {
// Only allow depth override within REST.
// If allowed in GraphQL, it would break resolvers
// as a full object will be returned instead of an ID string
if (api === 'REST') {
if (payloadAPI === 'REST') {
if (depth && depth !== '0') {
queryOptionsToExecute.options.autopopulate = {
maxDepth: parseInt(depth, 10),

View File

@@ -11,13 +11,7 @@ const update = async (args) => {
await executePolicy(args, args.config.policies.update);
let options = {
Model: args.Model,
locale: args.locale,
fallbackLocale: args.fallbackLocale,
id: args.id,
data: args.data,
};
let options = { ...args };
// /////////////////////////////////////
// 2. Validate incoming data
@@ -48,8 +42,10 @@ const update = async (args) => {
const {
Model,
id,
locale,
fallbackLocale,
req: {
locale,
fallbackLocale,
},
data,
} = options;

View File

@@ -6,12 +6,10 @@ const { create } = require('../operations');
const createHandler = async (req, res) => {
try {
const doc = await create({
req,
Model: req.Model,
config: req.collection,
user: req.user,
data: req.body,
locale: req.locale,
api: 'REST',
});
return res.status(httpStatus.CREATED).json({

View File

@@ -5,13 +5,10 @@ const { deleteQuery } = require('../operations');
const deleteHandler = async (req, res) => {
try {
const doc = await deleteQuery({
req,
Model: req.Model,
config: req.collection,
user: req.user,
id: req.params.id,
locale: req.locale,
fallbackLocale: req.fallbackLocale,
api: 'REST',
});
if (!doc) {

View File

@@ -4,17 +4,14 @@ const { find } = require('../operations');
const findHandler = async (req, res) => {
try {
const options = {
req,
Model: req.Model,
config: req.collection,
where: req.query.where,
locale: req.locale,
fallbackLocale: req.fallbackLocale,
page: req.query.page,
limit: req.query.limit,
sort: req.query.sort,
depth: req.query.depth,
user: req.user,
api: 'REST',
};
const result = await find(options);

View File

@@ -4,14 +4,11 @@ const formatErrorResponse = require('../../express/responses/formatError');
const findByIDHandler = async (req, res) => {
const options = {
req,
Model: req.Model,
config: req.collection,
user: req.user,
id: req.params.id,
locale: req.locale,
fallbackLocale: req.fallbackLocale,
depth: req.query.depth,
api: 'REST',
};
try {

View File

@@ -6,13 +6,11 @@ const { update } = require('../operations');
const updateHandler = async (req, res) => {
try {
const doc = await update({
req,
Model: req.Model,
config: req.collection,
user: req.user,
id: req.params.id,
data: req.body,
locale: req.locale,
api: 'REST',
});
return res.status(httpStatus.OK).json({

View File

@@ -0,0 +1,8 @@
const identifyAPI = (api) => {
return (req, _, next) => {
req.payloadAPI = api;
next();
};
};
module.exports = identifyAPI;

View File

@@ -6,6 +6,7 @@ const methodOverride = require('method-override');
const cookieParser = require('cookie-parser');
const localizationMiddleware = require('../../localization/middleware');
const authenticate = require('./authenticate');
const identifyAPI = require('./identifyAPI');
const middleware = (config) => {
return [
@@ -19,6 +20,7 @@ const middleware = (config) => {
compression(config.compression),
localizationMiddleware(config.localization),
authenticate,
identifyAPI('REST'),
(req, res, next) => {
if (config.cors) {
if (config.cors.indexOf(req.headers.origin) > -1) {

View File

@@ -2,7 +2,7 @@ const { Schema } = require('mongoose');
const formatBaseSchema = (field) => {
return {
hide: field.hide || false,
hidden: field.hidden || false,
localized: field.localized || false,
unique: field.unique || false,
required: (field.required && !field.localized) || false,
@@ -32,9 +32,6 @@ const fieldToSchemaMap = {
checkbox: (field) => {
return { ...formatBaseSchema(field), type: Boolean };
},
// hidden: (field) => {
// return { ...formatBaseSchema(field), type: String };
// },
date: (field) => {
return {
...formatBaseSchema(field),

View File

@@ -2,6 +2,9 @@
const { findOne } = require('../../operations');
const findOneResolver = (Model, config) => async (_, args, context) => {
if (args.locale) context.locale = args.locale;
if (args.fallbackLocale) context.fallbackLocale = args.fallbackLocale;
const { slug } = config;
const options = {
@@ -9,20 +12,9 @@ const findOneResolver = (Model, config) => async (_, args, context) => {
config,
slug,
depth: 0,
api: 'GraphQL',
user: context.user,
req: context,
};
if (args.locale) {
context.locale = args.locale;
options.locale = args.locale;
}
if (args.fallbackLocale) {
context.fallbackLocale = args.fallbackLocale;
options.fallbackLocale = args.fallbackLocale;
}
const result = await findOne(options);
return result;
};

View File

@@ -2,30 +2,20 @@
const { upsert } = require('../../operations');
const upsertResolver = (Model, config) => async (_, args, context) => {
if (args.locale) context.locale = args.locale;
if (args.fallbackLocale) context.fallbackLocale = args.fallbackLocale;
const { slug } = config;
const options = {
config,
Model,
locale: context.locale,
fallbackLocale: context.fallbackLocale,
data: args.data,
slug,
depth: 0,
api: 'GraphQL',
user: context.user,
req: context,
};
if (args.locale) {
context.locale = args.locale;
options.locale = args.locale;
}
if (args.fallbackLocale) {
context.fallbackLocale = args.fallbackLocale;
options.fallbackLocale = args.fallbackLocale;
}
const result = await upsert(options);
return result;

View File

@@ -28,11 +28,13 @@ const findOne = async (args) => {
const {
depth,
api,
Model,
locale,
slug,
fallbackLocale,
req: {
payloadAPI,
locale,
fallbackLocale,
},
} = options;
const queryOptionsToExecute = {
@@ -42,7 +44,7 @@ const findOne = async (args) => {
// Only allow depth override within REST.
// If allowed in GraphQL, it would break resolvers
// as a full object will be returned instead of an ID string
if (api === 'REST') {
if (payloadAPI === 'REST') {
if (depth && depth !== '0') {
queryOptionsToExecute.options.autopopulate = {
maxDepth: parseInt(depth, 10),

View File

@@ -35,10 +35,12 @@ const upsert = async (args) => {
const {
Model,
locale,
slug,
fallbackLocale,
data,
req: {
locale,
fallbackLocale,
},
} = options;

View File

@@ -7,14 +7,11 @@ const findOneHandler = (Model, config) => async (req, res) => {
const { slug } = config;
const result = await findOne({
req,
Model,
config,
slug,
depth: req.query.depth,
locale: req.locale,
fallbackLocale: req.fallbackLocale,
api: 'REST',
user: req.user,
});
return res.status(httpStatus.OK).json(result);

View File

@@ -7,14 +7,11 @@ const upsertHandler = (Model, config) => async (req, res) => {
const { slug } = config;
const result = await upsert({
req,
Model,
config,
slug,
depth: req.query.depth,
locale: req.locale,
fallbackLocale: req.fallbackLocale,
api: 'REST',
user: req.user,
});
return res.status(httpStatus.OK).json({ message: 'Global saved successfully.', result });

View File

@@ -15,6 +15,7 @@ const registerGlobals = require('./globals/register');
const GraphQL = require('./graphql');
const sanitizeConfig = require('./utilities/sanitizeConfig');
const buildEmail = require('./email/build');
const identifyAPI = require('./express/middleware/identifyAPI');
class Payload {
constructor(options) {
@@ -56,6 +57,7 @@ class Payload {
// Init GraphQL
this.router.use(
this.config.routes.graphQL,
identifyAPI('GraphQL'),
createAuthHeaderFromCookie,
authenticate,
new GraphQL(this.config, this.collections, this.User, this.Upload, this.globals).init(),

View File

@@ -2,12 +2,12 @@ module.exports = [
{
name: 'resetPasswordToken',
type: 'text',
// hide: true,
hidden: true,
},
{
name: 'resetPasswordExpiration',
type: 'date',
// hide: true,
hidden: true,
},
{
name: 'enableAPIKey',

View File

@@ -11,7 +11,7 @@ const executePolicy = async (operation, policy) => {
return true;
}
if (operation.user) {
if (operation.req.user) {
return true;
}

View File

@@ -163,6 +163,14 @@ function registerUser() {
resolve: register(this.User),
};
this.Mutation.fields.forgotPassword = {
type: new GraphQLNonNull(GraphQLBoolean),
args: {
[useAsUsername]: { type: new GraphQLNonNull(GraphQLString) },
},
resolve: forgotPassword(this.User, this.email),
};
this.Mutation.fields.resetPassword = {
type: this.User.graphQL.type,
args: {
@@ -172,14 +180,6 @@ function registerUser() {
resolve: resetPassword(this.User),
};
this.Mutation.fields.forgotPassword = {
type: new GraphQLNonNull(GraphQLBoolean),
args: {
[useAsUsername]: { type: GraphQLString },
},
resolve: forgotPassword(this.User, this.email),
};
this.Mutation.fields.refreshToken = {
type: GraphQLString,
resolve: refresh(this.User),

View File

@@ -1,14 +1,12 @@
/* eslint-disable no-param-reassign */
const { forgotPassword } = require('../../operations');
const forgotPasswordResolver = ({ Model, config }, email) => async (_, args, context) => {
const forgotPasswordResolver = (config, email) => async (_, args, context) => {
const options = {
Model,
config,
data: args,
api: 'GraphQL',
user: context.user,
email,
req: context,
};
const result = await forgotPassword(options);

View File

@@ -4,8 +4,7 @@ const { init } = require('../../operations');
const initResolver = ({ Model }) => async (_, __, context) => {
const options = {
Model,
api: 'GraphQL',
user: context.user,
req: context,
};
const result = await init(options);

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-param-reassign */
const { login } = require('../../operations');
const loginResolver = ({ Model, config }) => async (_, args) => {
const loginResolver = ({ Model, config }) => async (_, args, context) => {
const usernameField = config.auth.useAsUsername;
const options = {
Model,
@@ -10,7 +10,7 @@ const loginResolver = ({ Model, config }) => async (_, args) => {
[usernameField]: args[usernameField],
password: args.password,
},
api: 'GraphQL',
req: context,
};
const token = await login(options);

View File

@@ -6,7 +6,7 @@ const refreshResolver = ({ Model, config }) => async (_, __, context) => {
config,
Model,
authorization: context.headers.authorization,
api: 'GraphQL',
req: context,
};
const refreshedToken = await refresh(options);

View File

@@ -6,10 +6,8 @@ const registerResolver = ({ Model, config }) => async (_, args, context) => {
config,
Model,
data: args.data,
api: 'GraphQL',
locale: context.locale,
fallbackLocale: context.fallbackLocale,
depth: 0,
req: context,
};
if (args.locale) {

View File

@@ -2,10 +2,14 @@
const { resetPassword } = require('../../operations');
const resetPasswordResolver = ({ Model, config }) => async (_, args, context) => {
if (args.locale) context.locale = args.locale;
if (args.fallbackLocale) context.fallbackLocale = args.fallbackLocale;
const options = {
Model,
config,
data: args,
req: context,
api: 'GraphQL',
user: context.user,
};

View File

@@ -2,28 +2,18 @@
const { update } = require('../../operations');
const updateResolver = ({ Model, config }) => async (_, args, context) => {
if (args.locale) context.locale = args.locale;
if (args.fallbackLocale) context.fallbackLocale = args.fallbackLocale;
const options = {
config,
Model,
data: args.data,
id: args.id,
api: 'GraphQL',
user: context.user,
locale: context.locale,
fallbackLocale: context.fallbackLocale,
depth: 0,
req: context,
};
if (args.locale) {
context.locale = args.locale;
options.locale = args.locale;
}
if (args.fallbackLocale) {
context.fallbackLocale = args.fallbackLocale;
options.fallbackLocale = args.fallbackLocale;
}
const user = await update(options);
return user;

View File

@@ -9,13 +9,7 @@ const forgotPassword = async (args) => {
throw new APIError('Missing username.');
}
let options = {
Model: args.Model,
config: args.config,
api: args.api,
data: args.data,
email: args.email,
};
let options = { ...args };
// /////////////////////////////////////
// 1. Execute before login hook

View File

@@ -5,12 +5,7 @@ const login = async (args) => {
try {
// Await validation here
let options = {
Model: args.Model,
config: args.config,
api: args.api,
data: args.data,
};
let options = { ...args };
// /////////////////////////////////////
// 1. Execute before login hook

View File

@@ -4,11 +4,7 @@ const refresh = async (args) => {
try {
// Await validation here
let options = {
config: args.config,
api: args.api,
authorization: args.authorization,
};
let options = { ...args };
// /////////////////////////////////////
// 1. Execute before refresh hook

View File

@@ -13,14 +13,7 @@ const register = async (args) => {
await executePolicy(args, args.config.policies.register);
}
let options = {
Model: args.Model,
config: args.config,
api: args.api,
data: args.data,
locale: args.locale,
fallbackLocale: args.fallbackLocale,
};
let options = { ...args };
// /////////////////////////////////////
// 2. Validate incoming data
@@ -51,8 +44,10 @@ const register = async (args) => {
const {
Model,
data,
locale,
fallbackLocale,
req: {
locale,
fallbackLocale,
},
} = options;
const modelData = { ...data };

View File

@@ -10,12 +10,7 @@ const registerFirstUser = async (args) => {
// Await validation here
let options = {
Model: args.Model,
config: args.config,
api: args.api,
data: args.data,
};
let options = { ...args };
// /////////////////////////////////////
// 1. Execute before register first user hook

View File

@@ -8,13 +8,7 @@ const resetPassword = async (args) => {
throw new APIError('Missing required data.');
}
let options = {
Model: args.Model,
config: args.config,
api: args.api,
data: args.data,
user: args.user,
};
let options = { ...args };
// /////////////////////////////////////
// 1. Execute before reset password hook

View File

@@ -12,15 +12,7 @@ const update = async (args) => {
// Await validation here
let options = {
Model: args.Model,
config: args.config,
api: args.api,
data: args.data,
id: args.id,
locale: args.locale,
fallbackLocale: args.fallbackLocale,
};
let options = { ...args };
// /////////////////////////////////////
// 1. Execute before update hook
@@ -40,8 +32,10 @@ const update = async (args) => {
Model,
data,
id,
locale,
fallbackLocale,
req: {
locale,
fallbackLocale,
},
} = options;
const dataToUpdate = { ...data };

View File

@@ -5,9 +5,9 @@ const { forgotPassword } = require('../operations');
const forgotPasswordHandler = (config, email) => async (req, res) => {
try {
await forgotPassword({
req,
Model: req.Model,
config,
api: 'REST',
data: req.body,
email,
});

View File

@@ -5,10 +5,10 @@ const { login } = require('../operations');
const loginHandler = async (req, res) => {
try {
const token = await login({
req,
Model: req.Model,
config: req.collection,
data: req.body,
api: 'REST',
});
return res.status(httpStatus.OK)

View File

@@ -5,10 +5,9 @@ const { refresh } = require('../operations');
const refreshHandler = async (req, res) => {
try {
const refreshedToken = await refresh({
req,
config: req.collection,
api: 'REST',
authorization: req.headers.authorization,
user: req.user,
});
return res.status(200).json({

View File

@@ -5,13 +5,10 @@ const { register } = require('../operations');
const registerHandler = async (req, res) => {
try {
const user = await register({
req,
data: req.body,
Model: req.Model,
config: req.collection,
api: 'REST',
locale: req.locale,
fallbackLocale: req.fallbackLocale,
user: req.user,
});
return res.status(201).json(user);

View File

@@ -5,9 +5,9 @@ const { registerFirstUser } = require('../operations');
const registerFirstUserHandler = async (req, res) => {
try {
const firstUser = await registerFirstUser({
req,
Model: req.Model,
config: req.collection,
api: 'REST',
data: req.body,
});

View File

@@ -5,10 +5,9 @@ const { resetPassword } = require('../operations');
const resetPasswordHandler = async (req, res) => {
try {
const token = await resetPassword({
req,
Model: req.Model,
config: req.collection,
data: req.body,
api: 'REST',
});
return res.status(httpStatus.OK)

View File

@@ -6,14 +6,11 @@ const { update } = require('../operations');
const updateHandler = async (req, res) => {
try {
const user = await update({
req,
data: req.body,
Model: req.Model,
config: req.collection,
id: req.params.id,
api: 'REST',
locale: req.locale,
fallbackLocale: req.fallbackLocale,
user: req.user,
});
return res.status(httpStatus.OK).json({

View File

@@ -7,6 +7,7 @@ const { email, password } = require('../tests/credentials');
*/
const config = require('../../demo/payload.config');
const { payload } = require('../../demo/server');
const url = config.serverURL;
const usernameField = config.user.auth.useAsUsername;
@@ -79,6 +80,25 @@ describe('Users REST API', () => {
token = data.refreshedToken;
});
it('should allow forgot-password by email', async () => {
// TODO: figure out how to spy on payload instance functions
// const mailSpy = jest.spyOn(payload, 'sendEmail');
const response = await fetch(`${url}/api/forgot-password`, {
method: 'post',
body: JSON.stringify({
[usernameField]: email,
}),
headers: {
'Content-Type': 'application/json',
},
});
// is not working
// expect(mailSpy).toHaveBeenCalled();
expect(response.status).toBe(200);
});
it('should allow a user to be created', async () => {
const response = await fetch(`${url}/api/users/register`, {
body: JSON.stringify({