const Feedback = require('./Feedback'); const errors = require('@tryghost/errors'); const tpl = require('@tryghost/tpl'); const messages = { invalidScore: 'Invalid feedback score. Only 1 or 0 is currently allowed.', postNotFound: 'Post not found.', memberNotFound: 'Member not found.' }; /** * @typedef {object} IFeedbackRepository * @prop {(feedback: Feedback) => Promise} add * @prop {(feedback: Feedback) => Promise} edit * @prop {(postId, memberId) => Promise} get * @prop {(id: string) => Promise} getPostById */ class AudienceFeedbackController { /** @type IFeedbackRepository */ #repository; /** * @param {object} deps * @param {IFeedbackRepository} deps.repository */ constructor(deps) { this.#repository = deps.repository; } /** * Get member from frame */ #getMember(frame) { if (!frame.options?.context?.member?.id) { // This is an internal server error because authentication should happen outside this service. throw new errors.InternalServerError({ message: tpl(messages.memberNotFound) }); } return frame.options.context.member; } async add(frame) { const data = frame.data.feedback[0]; const postId = data.post_id; const score = data.score; if (![0, 1].includes(score)) { throw new errors.ValidationError({ message: tpl(messages.invalidScore) }); } const member = this.#getMember(frame); const post = await this.#repository.getPostById(postId); if (!post) { throw new errors.NotFoundError({ message: tpl(messages.postNotFound) }); } const existing = await this.#repository.get(post.id, member.id); if (existing) { if (existing.score === score) { // Don't save so we don't update the updated_at timestamp return existing; } existing.score = score; await this.#repository.edit(existing); return existing; } const feedback = new Feedback({ memberId: member.id, postId: post.id, score }); await this.#repository.add(feedback); return feedback; } } module.exports = AudienceFeedbackController;