From 8171abfa6dea5710c2bb1e652a423632fc8fc381 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Fri, 26 May 2023 12:26:26 +0400 Subject: [PATCH] feat(api): reset progress endpoint (#50432) --------- Co-authored-by: Oliver Eyton-Williams --- api/src/routes/user.test.ts | 79 +++++++++++++++++++++++++++++++++---- api/src/routes/user.ts | 72 ++++++++++++++++++++++++++------- api/src/schemas.ts | 23 +++++++++++ 3 files changed, 152 insertions(+), 22 deletions(-) diff --git a/api/src/routes/user.test.ts b/api/src/routes/user.test.ts index 9683a5855a6..27acf07889b 100644 --- a/api/src/routes/user.test.ts +++ b/api/src/routes/user.test.ts @@ -1,17 +1,48 @@ -import request from 'supertest'; - import { setupServer, superRequest } from '../../jest.utils'; +const baseProgressData = { + currentChallengeId: '', + isRespWebDesignCert: false, + is2018DataVisCert: false, + isFrontEndLibsCert: false, + isJsAlgoDataStructCert: false, + isApisMicroservicesCert: false, + isInfosecQaCert: false, + isQaCertV7: false, + isInfosecCertV7: false, + is2018FullStackCert: false, + isFrontEndCert: false, + isBackEndCert: false, + isDataVisCert: false, + isFullStackCert: false, + isSciCompPyCertV7: false, + isDataAnalysisPyCertV7: false, + isMachineLearningPyCertV7: false, + isRelationalDatabaseCertV8: false, + isCollegeAlgebraPyCertV8: false, + completedChallenges: [], + savedChallenges: [], + partiallyCompletedChallenges: [], + needsModeration: false +}; + +const modifiedProgressData = { + ...baseProgressData, + currentChallengeId: 'hello there', + isRespWebDesignCert: true, + isJsAlgoDataStructCert: true, + isRelationalDatabaseCertV8: true, + needsModeration: true +}; + describe('userRoutes', () => { setupServer(); describe('Authenticated user', () => { let setCookies: string[]; - beforeAll(async () => { - const res = await request(fastifyTestInstance?.server).get( - '/auth/dev-callback' - ); + beforeEach(async () => { + const res = await superRequest('/auth/dev-callback', { method: 'GET' }); setCookies = res.get('Set-Cookie'); }); @@ -22,7 +53,7 @@ describe('userRoutes', () => { setCookies }); - const userCount = await fastifyTestInstance?.prisma.user.count({ + const userCount = await fastifyTestInstance.prisma.user.count({ where: { email: 'foo@bar.com' } }); @@ -31,6 +62,30 @@ describe('userRoutes', () => { expect(userCount).toBe(0); }); }); + + describe('/account/reset-progress', () => { + test('POST returns 200 status code with empty object', async () => { + await fastifyTestInstance.prisma.user.updateMany({ + where: { email: 'foo@bar.com' }, + data: modifiedProgressData + }); + + const response = await superRequest('/account/reset-progress', { + method: 'POST', + setCookies + }); + + const user = await fastifyTestInstance.prisma.user.findFirst({ + where: { email: 'foo@bar.com' } + }); + + expect(response.status).toBe(200); + expect(response.body).toStrictEqual({}); + + expect(user?.progressTimestamps).toHaveLength(1); + expect(user).toMatchObject(baseProgressData); + }); + }); }); describe('Unauthenticated user', () => { @@ -44,5 +99,15 @@ describe('userRoutes', () => { expect(response?.statusCode).toBe(401); }); }); + + describe('/account/reset-progress', () => { + test('POST returns 401 status code with error message', async () => { + const response = await superRequest('/account/reset-progress', { + method: 'POST' + }); + + expect(response?.statusCode).toBe(401); + }); + }); }); }); diff --git a/api/src/routes/user.ts b/api/src/routes/user.ts index 954040be1c7..da12cef1617 100644 --- a/api/src/routes/user.ts +++ b/api/src/routes/user.ts @@ -1,7 +1,6 @@ -import { - Type, - type FastifyPluginCallbackTypebox -} from '@fastify/type-provider-typebox'; +import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox'; + +import { schemas } from '../schemas'; export const userRoutes: FastifyPluginCallbackTypebox = ( fastify, @@ -13,17 +12,7 @@ export const userRoutes: FastifyPluginCallbackTypebox = ( fastify.post( '/account/delete', { - schema: { - response: { - 200: Type.Object({}), - 500: Type.Object({ - message: Type.Literal( - 'Oops! Something went wrong. Please try again in a moment or contact support@freecodecamp.org if the error persists.' - ), - type: Type.Literal('danger') - }) - } - } + schema: schemas.deleteMyAccount }, async (req, reply) => { try { @@ -49,5 +38,58 @@ export const userRoutes: FastifyPluginCallbackTypebox = ( } ); + fastify.post( + '/account/reset-progress', + { + schema: schemas.resetMyProgress + }, + async (req, reply) => { + try { + await fastify.prisma.userToken.deleteMany({ + where: { userId: req.session.user.id } + }); + await fastify.prisma.user.update({ + where: { id: req.session.user.id }, + data: { + progressTimestamps: [Date.now()], + currentChallengeId: '', + isRespWebDesignCert: false, + is2018DataVisCert: false, + isFrontEndLibsCert: false, + isJsAlgoDataStructCert: false, + isApisMicroservicesCert: false, + isInfosecQaCert: false, + isQaCertV7: false, + isInfosecCertV7: false, + is2018FullStackCert: false, + isFrontEndCert: false, + isBackEndCert: false, + isDataVisCert: false, + isFullStackCert: false, + isSciCompPyCertV7: false, + isDataAnalysisPyCertV7: false, + isMachineLearningPyCertV7: false, + isRelationalDatabaseCertV8: false, + isCollegeAlgebraPyCertV8: false, + completedChallenges: [], + savedChallenges: [], + partiallyCompletedChallenges: [], + needsModeration: false + } + }); + + return {}; + } catch (err) { + fastify.log.error(err); + void reply.code(500); + return { + message: + 'Oops! Something went wrong. Please try again in a moment or contact support@freecodecamp.org if the error persists.', + type: 'danger' + }; + } + } + ); + done(); }; diff --git a/api/src/schemas.ts b/api/src/schemas.ts index 0ebb622b895..3fcd2928c69 100644 --- a/api/src/schemas.ts +++ b/api/src/schemas.ts @@ -123,6 +123,29 @@ export const schemas = { }) } }, + // User: + deleteMyAccount: { + response: { + 200: Type.Object({}), + 500: Type.Object({ + message: Type.Literal( + 'Oops! Something went wrong. Please try again in a moment or contact support@freecodecamp.org if the error persists.' + ), + type: Type.Literal('danger') + }) + } + }, + resetMyProgress: { + response: { + 200: Type.Object({}), + 500: Type.Object({ + message: Type.Literal( + 'Oops! Something went wrong. Please try again in a moment or contact support@freecodecamp.org if the error persists.' + ), + type: Type.Literal('danger') + }) + } + }, // Deprecated endpoints: deprecatedEndpoints: { response: {