UNPKG

scuttle-poll

Version:

Create and vote on polls on ssb

144 lines (118 loc) 4.11 kB
const getContent = require('ssb-msg-content') const { isPoll, isPosition } = require('ssb-poll-schema') const PositionChoiceError = require('../../errors/sync/positionChoiceError') const PositionLateError = require('../../errors/sync/positionLateError') const PositionTypeError = require('../../errors/sync/positionTypeError') // Expects `poll` and `position` objects passed in to be of shape: // { // key, // value: { // content: {...}, // timestamp: ... // author: ... // } // } // // postions must be of the correct type ie: type checked by the caller. // TODO find a better home for this (it is not strongly in poll nor position domain) module.exports = function ({positions, poll}) { if (isPoll.chooseOne(poll)) { return chooseOneResults({positions, poll}) } if (isPoll.meetingTime(poll)) { return meetingTimeResults({positions, poll}) } return { results: [], errors: [] } } function meetingTimeResults ({positions, poll}) { // NOTE - patchbay-scry modifies these results a lot for it's display // consider pulling those definitions in here? const { choices } = getContent(poll).details var results = choices .map(choice => { return { choice: new Date(choice), voters: {} } }) return positions.reduce((acc, position) => { const { author } = position.value const { choices } = getContent(position).details // << note multiple choices if (isInvalidType({position, poll})) { acc.errors.push(PositionTypeError({position})) return acc } if (isInvalidChoices({position, poll})) { acc.errors.push(PositionChoiceError({position})) return acc } if (isPositionLate({position, poll})) { acc.errors.push(PositionLateError({position})) return acc } deleteExistingVotesByAuthor({results: acc.results, author}) choices.forEach(choice => { acc.results[choice].voters[author] = position }) return acc }, {errors: [], results}) function isInvalidChoices ({position, poll}) { const { choices } = position.value.content.details // TODO: this is fragile. We should be using a parsed or decorated poll return choices.some(choice => { return choice > poll.value.content.details.choices.length - 1 }) } } function chooseOneResults ({positions, poll}) { const { choices } = getContent(poll).details var results = choices .map(choice => { return { choice, voters: {} } }) return positions.reduce((acc, position) => { const { author } = position.value const { choice } = getContent(position).details // << note singular choice if (isInvalidType({position, poll})) { acc.errors.push(PositionTypeError({position})) return acc } if (isInvalidChoice({position, poll})) { acc.errors.push(PositionChoiceError({position})) return acc } if (isPositionLate({position, poll})) { acc.errors.push(PositionLateError({position})) return acc } deleteExistingVotesByAuthor({results: acc.results, author}) acc.results[choice].voters[author] = position return acc }, {errors: [], results}) function isInvalidChoice ({position, poll}) { const { choice } = position.value.content.details // TODO: this is fragile. We should be using a parsed or decorated poll return choice > poll.value.content.details.choices.length - 1 } } // !!! assumes these are already sorted by time. // modifies results passed in function deleteExistingVotesByAuthor ({author, results}) { results.forEach(result => { if (result.voters[author]) { delete result.voters[author] } }) } function isInvalidType ({position, poll}) { // TODO:this is fragile. We should be using a parsed or decorated poll const pollType = poll.value.content.details.type return !isPosition[pollType](position) } function isPositionLate ({position, poll}) { // TODO:this is fragile. We should be using a parsed or decorated poll return new Date(position.value.timestamp) > new Date(poll.value.content.closesAt) }