Skip to content

Commit

Permalink
Merge pull request #12 from solocommand/custom-text-field
Browse files Browse the repository at this point in the history
Custom text field support
  • Loading branch information
solocommand authored Aug 16, 2024
2 parents a1fd266 + fd65698 commit d7060af
Show file tree
Hide file tree
Showing 26 changed files with 673 additions and 19 deletions.
28 changes: 26 additions & 2 deletions services/application/src/actions/field/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { handleError } = require('@identity-x/utils').mongoose;
const { Application } = require('../../mongodb/models');
const BooleanField = require('../../mongodb/models/field/boolean');
const SelectField = require('../../mongodb/models/field/select');
const TextField = require('../../mongodb/models/field/text');
const prepareExternalId = require('./utils/prepare-external-id');

const createBoolean = async ({
Expand Down Expand Up @@ -56,12 +57,33 @@ const createSelect = async ({
return select;
};

const createText = async ({
application,
name,
label,
required,
active,
externalId: eid,
} = {}) => {
const externalId = prepareExternalId(eid);
const text = new TextField({
applicationId: application._id,
name,
label,
required,
active,
externalId,
});
await text.save();
return text;
};

module.exports = async ({
type,
applicationId,
payload = {},
} = {}) => {
const supportedTypes = ['select', 'boolean'];
const supportedTypes = ['select', 'boolean', 'text'];
if (!supportedTypes.includes(type)) throw createParamError('type', type, supportedTypes);
if (!applicationId) throw createRequiredParamError('applicationId');

Expand All @@ -73,8 +95,10 @@ module.exports = async ({
switch (type) {
case 'boolean':
return createBoolean({ ...payload, application });
default:
case 'select':
return createSelect({ ...payload, application });
default:
return createText({ ...payload, application });
}
} catch (e) {
throw handleError(createError, e);
Expand Down
9 changes: 7 additions & 2 deletions services/application/src/actions/field/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,31 @@ const create = require('./create');
const updateOne = require('./update-one');
const userBooleanAnswers = require('./user-boolean-answers');
const userSelectAnswers = require('./user-select-answers');
const userTextAnswers = require('./user-text-answers');

const Field = require('../../mongodb/models/field');
const SelectField = require('../../mongodb/models/field/select');
const BooleanField = require('../../mongodb/models/field/boolean');
const TextField = require('../../mongodb/models/field/text');

module.exports = {
create,
findById: ({ id, type, fields }) => {
const supportedTypes = ['select', 'boolean'];
const supportedTypes = ['select', 'boolean', 'text'];
if (!supportedTypes.includes(type)) throw createParamError('type', type, supportedTypes);
switch (type) {
case 'boolean':
return findById(BooleanField, { id, fields });
default:
case 'select':
return findById(SelectField, { id, fields });
default:
return findById(TextField, { id, fields });
}
},
listForApp: params => listForApp(Field, params),
matchForApp: params => matchForApp(Field, params),
updateOne,
userBooleanAnswers,
userSelectAnswers,
userTextAnswers,
};
37 changes: 34 additions & 3 deletions services/application/src/actions/field/update-one.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { handleError } = require('@identity-x/utils').mongoose;
const { Application } = require('../../mongodb/models');
const BooleanField = require('../../mongodb/models/field/boolean');
const SelectField = require('../../mongodb/models/field/select');
const TextField = require('../../mongodb/models/field/text');
const prepareExternalId = require('./utils/prepare-external-id');

const updateSelect = async ({
Expand Down Expand Up @@ -93,6 +94,35 @@ const updateBoolean = async ({
await boolean.save();
return boolean;
};
const updateText = async ({
id,
application,
payload,
} = {}) => {
const text = await TextField.findByIdForApp(id, application._id);
if (!text) throw createError(404, `No text field was found for '${id}'`);

const {
name,
label,
required,
active,
externalId: eid,
} = payload;

const externalId = prepareExternalId(eid);

text.set({
name,
label,
required,
active,
externalId,
});

await text.save();
return text;
};

module.exports = async ({
id,
Expand All @@ -101,20 +131,21 @@ module.exports = async ({
payload = {},
} = {}) => {
if (!id) throw createRequiredParamError('id');
const supportedTypes = ['select', 'boolean'];
const supportedTypes = ['select', 'boolean', 'text'];
if (!supportedTypes.includes(type)) throw createParamError('type', type, supportedTypes);
if (!applicationId) throw createRequiredParamError('applicationId');

const application = await Application.findById(applicationId, ['id']);
if (!application) throw createError(404, `No application was found for '${applicationId}'`);

// for now, only select field types are supported.
try {
switch (type) {
case 'boolean':
return updateBoolean({ id, application, payload });
default:
case 'select':
return updateSelect({ id, application, payload });
default:
return updateText({ id, application, payload });
}
} catch (e) {
throw handleError(createError, e);
Expand Down
45 changes: 45 additions & 0 deletions services/application/src/actions/field/user-text-answers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const { Sort } = require('@identity-x/pagination');
const TextField = require('../../mongodb/models/field/text');

const { isArray } = Array;

module.exports = async ({
applicationId,
fieldIds,
customTextFieldAnswers,
onlyAnswered,
onlyActive,
sort,
} = {}) => {
const $sort = new Sort(sort);
const fieldQuery = {
applicationId,
...(isArray(fieldIds) && fieldIds.length && { _id: { $in: fieldIds } }),
...(onlyActive && { active: { $ne: false } }),
};
const fields = await TextField.find(fieldQuery, {}, { sort: $sort.value });
// return nothing when no custom booleans are found.
if (!fields.length) return [];

// ensure answers are an array
const customFieldAnswers = !isArray(customTextFieldAnswers)
|| !customTextFieldAnswers.length
? []
: customTextFieldAnswers;


// return nothing when no answers are found and only answered questions have been requested.
if (onlyAnswered && !customFieldAnswers.length) return [];

const mapped = fields.map((field) => {
const fieldAnswer = customFieldAnswers.find(answer => `${answer._id}` === `${field._id}`);
const value = fieldAnswer ? fieldAnswer.value : null;
return {
id: field._id,
field,
hasAnswered: Boolean(fieldAnswer),
value,
};
});
return onlyAnswered ? mapped.filter(field => field.hasAnswered) : mapped;
};
2 changes: 2 additions & 0 deletions services/application/src/actions/user/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const setUnverifiedData = require('./set-unverified-data');
const updateCustomAttributes = require('./update-custom-attributes');
const updateCustomBooleanAnswers = require('./update-custom-boolean-answers');
const updateCustomSelectAnswers = require('./update-custom-select-answers');
const updateCustomTextAnswers = require('./update-custom-text-answers');
const updateOne = require('./update-one');
const verifyAuth = require('./verify-auth');

Expand Down Expand Up @@ -55,6 +56,7 @@ module.exports = {
updateCustomAttributes,
updateCustomBooleanAnswers,
updateCustomSelectAnswers,
updateCustomTextAnswers,
updateOne,
verifyAuth,
setLastSeen: async ({ id }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const { createError } = require('micro');
const { createRequiredParamError } = require('@base-cms/micro').service;
const { handleError } = require('@identity-x/utils').mongoose;

const { AppUser } = require('../../mongodb/models');

const { isArray } = Array;

module.exports = async ({
id,
applicationId,
answers,
profileLastVerifiedAt,
} = {}) => {
if (!id) throw createRequiredParamError('id');
if (!applicationId) throw createRequiredParamError('applicationId');

const user = await AppUser.findByIdForApp(id, applicationId);
if (!user) throw createError(404, `No user was found for '${id}'`);

// do not update user answers when passed answers are not an array
if (!isArray(answers)) return user;

// get all current answers as object { id, value }
const userObj = user.customTextFieldAnswers.reduce(
(obj, item) => ({ ...obj, [item._id]: item.value }), {},
);

// get new answers as object { id, value }
const newAnswers = answers.reduce(
(obj, item) => ({ ...obj, [item.fieldId]: item.value }), {},
);

// merge new and old ansers to account for old non active answers
const mergedAnswers = { ...userObj, ...newAnswers };

// convert merged answers into valid array of { _id, value } answers
const toSet = Object.keys(mergedAnswers).map((key) => {
const obj = { _id: key, value: mergedAnswers[key] };
return obj;
});

user.set('customTextFieldAnswers', toSet);
if (profileLastVerifiedAt) {
user.set('profileLastVerifiedAt', profileLastVerifiedAt);
user.set('forceProfileReVerification', false);
}
try {
await user.save();
return user;
} catch (e) {
throw handleError(createError, e);
}
};
4 changes: 4 additions & 0 deletions services/application/src/mongodb/models/field/text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const Field = require('./index');
const schema = require('../../schema/field/text');

module.exports = Field.discriminator('field-text', schema, 'text');
4 changes: 4 additions & 0 deletions services/application/src/mongodb/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ const AppUserLogin = require('./app-user-login');
const Segment = require('./segment');
const Comment = require('./comment');
const CommentStream = require('./comment-stream');
const FieldBoolean = require('./field/boolean');
const FieldSelect = require('./field/select');
const FieldText = require('./field/text');
const Team = require('./team');

module.exports = {
Expand All @@ -16,6 +18,8 @@ module.exports = {
Segment,
Comment,
CommentStream,
FieldBoolean,
FieldSelect,
FieldText,
Team,
};
18 changes: 18 additions & 0 deletions services/application/src/mongodb/schema/app-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ const customSelectFieldAnswerSchema = new Schema({
},
});

/**
* The built-in `_id` field of this sub-document represents
* the custom text field id.
*/
const customTextFieldAnswerSchema = new Schema({
/**
* The custom text field answer.
*/
value: {
type: Schema.Types.String,
trim: true,
},
});

const schema = new Schema({
email: {
type: String,
Expand Down Expand Up @@ -185,6 +199,10 @@ const schema = new Schema({
type: [customSelectFieldAnswerSchema],
default: () => [],
},
customTextFieldAnswers: {
type: [customTextFieldAnswerSchema],
default: () => [],
},
customAttributes: {
type: Object,
default: () => ({}),
Expand Down
5 changes: 5 additions & 0 deletions services/application/src/mongodb/schema/field/text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { Schema } = require('mongoose');

const schema = new Schema({});

module.exports = schema;
Loading

0 comments on commit d7060af

Please sign in to comment.