diff --git a/apps/api/__tests__/integration/surveys/clear-session.test.ts b/apps/api/__tests__/integration/surveys/clear-session.test.ts index 60c053194..1bb6a4359 100644 --- a/apps/api/__tests__/integration/surveys/clear-session.test.ts +++ b/apps/api/__tests__/integration/surveys/clear-session.test.ts @@ -48,10 +48,6 @@ export default () => { await suite.sharedTests.assertMissingAuthorization('delete', invalidUrl, { bearer: 'respondent' }); }); - it(`should return 404 when session record doesn't exist`, async () => { - await suite.sharedTests.assertMissingRecord('delete', url, { bearer: 'respondent' }); - }); - it('should return 204 and no content', async () => { await UserSurveySession.create(input); diff --git a/apps/api/src/http/routers/survey-respondent.router.ts b/apps/api/src/http/routers/survey-respondent.router.ts index 5f3657e45..0ef80107c 100644 --- a/apps/api/src/http/routers/survey-respondent.router.ts +++ b/apps/api/src/http/routers/survey-respondent.router.ts @@ -177,10 +177,10 @@ export function surveyRespondent() { return { status: 200, body: undefined }; }, - clearSession: async ({ params: { slug }, req }) => { + clearSession: async ({ params: { slug, sessionId }, req }) => { const { userId } = req.scope.cradle.user; - await req.scope.cradle.surveyService.clearSession(slug, userId); + await req.scope.cradle.surveyService.clearSession(slug, userId, sessionId); return { status: 204, body: undefined }; }, diff --git a/apps/api/src/jobs/surveys/survey-event-notification.ts b/apps/api/src/jobs/surveys/survey-event-notification.ts index be8fd8ceb..250ef7951 100644 --- a/apps/api/src/jobs/surveys/survey-event-notification.ts +++ b/apps/api/src/jobs/surveys/survey-event-notification.ts @@ -4,7 +4,7 @@ import axios from 'axios'; import type { IoC } from '@intake24/api/ioc'; import type { WebhookNotification } from '@intake24/common/types'; import { NotFoundError } from '@intake24/api/http/errors'; -import { submissionScope, Survey, SurveySubmission, UserSurveySession } from '@intake24/db'; +import { submissionScope, Survey, SurveySubmission } from '@intake24/db'; import NotificationJob from '../notification-job'; @@ -25,15 +25,6 @@ export default class SurveyEventNotification extends NotificationJob<'SurveyEven this.logger.debug('Job finished.'); } - private async getSession() { - const { sessionId, surveyId, userId } = this.params; - const session = await UserSurveySession.findOne({ where: { id: sessionId, surveyId, userId } }); - if (!session) - throw new NotFoundError('Session not found'); - - return undefined; - } - private async getSubmission(submissionId: string) { const submission = await SurveySubmission.findByPk(submissionId, submissionScope()); if (!submission) diff --git a/apps/api/src/services/survey/survey.service.ts b/apps/api/src/services/survey/survey.service.ts index ac5d019f8..e3cf38ad9 100644 --- a/apps/api/src/services/survey/survey.service.ts +++ b/apps/api/src/services/survey/survey.service.ts @@ -11,7 +11,7 @@ import type { SurveyRatingRequest, SurveyUserInfoResponse, } from '@intake24/common/types/http'; -import type { FindOptions, Includeable, SubmissionScope } from '@intake24/db'; +import type { FindOptions, SubmissionScope } from '@intake24/db'; import { ForbiddenError, NotFoundError } from '@intake24/api/http/errors'; import { jwt } from '@intake24/api/util'; import { strongPassword } from '@intake24/common/security'; @@ -307,26 +307,22 @@ function surveyService({ throw new NotFoundError(); const { id: surveyId, notifications, session } = survey; + const { uxSessionId } = sessionData; await UserSurveySession.destroy({ where: { userId, surveyId } }); if (session.store) { - await UserSurveySession.create({ id: sessionData.uxSessionId, userId, surveyId, sessionData }); + await UserSurveySession.create({ id: uxSessionId, userId, surveyId, sessionData }); } - if (notifications.length && notifications.some(({ type }) => type === 'survey.session.started') - ) { - await scheduler.jobs.addJob({ - type: 'SurveyEventNotification', - userId, - params: { - type: 'survey.session.started', - sessionId: sessionData.uxSessionId, - surveyId, - userId, - }, - }); - } + if (!notifications.some(({ type }) => type === 'survey.session.started')) + return; + + await scheduler.jobs.addJob({ + type: 'SurveyEventNotification', + userId, + params: { type: 'survey.session.started', sessionId: uxSessionId, surveyId, userId }, + }); }; /** @@ -361,30 +357,22 @@ function surveyService({ * * @param {string} slug * @param {string} userId + * @param {string} [clientSessionId] * @returns */ - const clearSession = async (slug: string, userId: string) => { + const clearSession = async (slug: string, userId: string, clientSessionId?: string) => { const survey = await Survey.findBySlug(slug, { attributes: ['id', 'notifications'] }); if (!survey) throw new NotFoundError(); const session = await UserSurveySession.findOne({ where: { userId, surveyId: survey.id } }); - if (!session) - throw new NotFoundError(); - const { - id: sessionId, - sessionData: { submissionTime }, - } = session; - await session.destroy(); - - if ( - submissionTime - || !survey.notifications.length - || !survey.notifications.some(({ type }) => type === 'survey.session.cancelled') - ) { + const hasNotifications = survey.notifications.some(({ type }) => type === 'survey.session.cancelled'); + const sessionId = session?.id ?? clientSessionId; + await session?.destroy(); + + if (session?.sessionData.submissionTime || !hasNotifications || !sessionId) return; - } await scheduler.jobs.addJob({ type: 'SurveyEventNotification', @@ -418,26 +406,24 @@ function surveyService({ const redirectPrompts = surveyScheme.prompts.submission.filter( (prompt): prompt is Prompts['redirect-prompt'] => prompt.component === 'redirect-prompt', ); - if (!redirectPrompts.length) + const size = redirectPrompts.length; + if (!size) return null; const identifiers = redirectPrompts.map(({ identifier }) => identifier); - const include: Includeable[] = [ - { association: 'aliases', where: { surveyId }, required: false }, - { association: 'customFields', where: { name: identifiers }, required: false }, - ]; - - const user = await User.findByPk(userId, { attributes: ['id'], include }); + const user = await User.findByPk(userId, { + attributes: ['id'], + include: [ + { association: 'aliases', where: { surveyId }, required: false }, + { association: 'customFields', where: { name: identifiers }, required: false }, + ], + }); if (!user) throw new NotFoundError(); const { aliases = [], customFields = [] } = user; - const size = redirectPrompts.length; - if (!size) - return null; - const urls = redirectPrompts.reduce>( (acc, { id, identifier, url }) => { let identifierValue: string | null; @@ -519,8 +505,14 @@ function surveyService({ }; const getSearchSettings = async (slug: string) => { - const survey = await Survey.findBySlug(slug, { attributes: ['searchSettings'], include: ['locale'] }); - if (!survey || !survey.locale) + const survey = await Survey.findBySlug(slug, { + attributes: ['searchSettings'], + include: [{ + association: 'locale', + attributes: ['code'], + }], + }); + if (!survey?.locale) throw new NotFoundError(); return { diff --git a/apps/survey/src/services/survey.service.ts b/apps/survey/src/services/survey.service.ts index 39d48879f..3a9e5b96d 100644 --- a/apps/survey/src/services/survey.service.ts +++ b/apps/survey/src/services/survey.service.ts @@ -74,15 +74,15 @@ export default { }, startUserSession: async (surveyId: string, session: SurveyState) => { - await http.post(`surveys/${surveyId}/session`, { session }); + await http.post(`surveys/${surveyId}/session`, { session }); }, saveUserSession: async (surveyId: string, session: SurveyState) => { - await http.put(`surveys/${surveyId}/session`, { session }); + await http.put(`surveys/${surveyId}/session`, { session }); }, - clearUserSession: async (surveyId: string) => { - await http.delete(`surveys/${surveyId}/session`); + clearUserSession: async (surveyId: string, sessionId?: string) => { + await http.delete(sessionId ? `surveys/${surveyId}/session/${sessionId}` : `surveys/${surveyId}/session`); }, submit: async (surveyId: string, submission: SurveyState): Promise => { diff --git a/apps/survey/src/stores/survey.ts b/apps/survey/src/stores/survey.ts index 9504147af..9a7db7a10 100644 --- a/apps/survey/src/stores/survey.ts +++ b/apps/survey/src/stores/survey.ts @@ -315,8 +315,10 @@ export const useSurvey = defineStore('survey', { }, async cancelRecall() { + const { uxSessionId } = this.data; + this.clearState(); - await this.clearUserSession(); + await this.clearUserSession(uxSessionId); }, setState(payload: CurrentSurveyState) { @@ -408,13 +410,13 @@ export const useSurvey = defineStore('survey', { await surveyService.saveUserSession(this.parameters.slug, this.data); }, - async clearUserSession() { + async clearUserSession(sessionId?: string) { if (!this.parameters) { console.error(`Survey parameters not loaded. Cannot clear user session.`); return; } - await surveyService.clearUserSession(this.parameters.slug); + await surveyService.clearUserSession(this.parameters.slug, sessionId); }, async submitRecall() { diff --git a/packages/common/src/contracts/survey-respondent.contract.ts b/packages/common/src/contracts/survey-respondent.contract.ts index 56bef394f..a0efbf098 100644 --- a/packages/common/src/contracts/survey-respondent.contract.ts +++ b/packages/common/src/contracts/survey-respondent.contract.ts @@ -72,7 +72,7 @@ export const surveyRespondent = initContract().router({ }, clearSession: { method: 'DELETE', - path: '/surveys/:slug/session', + path: '/surveys/:slug/session/:sessionId?', body: null, responses: { 204: z.undefined(),