From a9083eda59c8ff45fc8bad9303cdaa359eb0bb70 Mon Sep 17 00:00:00 2001 From: Max Greenwald Date: Thu, 7 Feb 2019 21:39:42 -0500 Subject: [PATCH] 1. BIG server clean & API response schema standardization (#266) :( * Update API response schema to always include 'status' and 'data' * Lots of schema changes and db migration to remove splash phot * Profile schema standardize select * Remove console log * Add ability to delete last photo * Remove last photo deletion status code * Fix delete photo changes to tests * Splash photo id nullable true on down migration * rollback last commit * splash photo nullable on down migration * fix weird flowtype issue in get-photo * add flowtype error comment * Update status with return flowtypes * Add comment about flowfixme issue * Update comments in api/users utils for select profile and settings * update async-handler comments --- src/server/api/auth/get-token-utln.js | 3 +- .../api/auth/send-verification-email.js | 20 +- src/server/api/auth/verify.js | 15 +- src/server/api/photos/confirm-upload.js | 46 +-- src/server/api/photos/delete-photo.js | 65 ++-- src/server/api/photos/get-photo.js | 25 +- src/server/api/photos/reorder-photos.js | 22 +- src/server/api/photos/sign-url.js | 5 +- src/server/api/relationships/block.js | 8 +- src/server/api/relationships/get-matches.js | 10 +- .../api/relationships/get-scene-candidates.js | 36 +- src/server/api/relationships/judge.js | 12 +- src/server/api/status-codes.js | 310 ++++++++++-------- ...y-profile.js => finalize-profile-setup.js} | 44 ++- src/server/api/users/get-my-photos.js | 7 +- src/server/api/users/get-my-settings.js | 28 +- src/server/api/users/get-profile.js | 36 +- src/server/api/users/index.js | 4 +- src/server/api/users/update-my-profile.js | 49 +-- src/server/api/users/update-my-settings.js | 44 +-- src/server/api/users/utils.js | 80 +++++ src/server/api/utils/async-handler.js | 6 +- .../api/utils/middleware/authenticated.js | 4 +- src/server/api/utils/middleware/hasProfile.js | 2 +- src/server/api/utils/status.js | 40 ++- src/server/api/utils/validate.js | 2 +- .../1549211629961_remove-splash-photo.js | 17 + src/server/db/pool.js | 2 +- src/server/docs/photos/confirm-upload.md | 5 +- src/server/docs/photos/delete-photo.md | 18 +- src/server/docs/photos/reorder-photos.md | 6 +- src/server/docs/photos/sign-url.md | 10 +- src/server/docs/relationships/get-matches.md | 24 +- .../relationships/get-scene-candidates.md | 19 +- ...y-profile.md => finalize-profile-setup.md} | 26 +- src/server/docs/users/get-my-photos.md | 2 +- src/server/docs/users/get-my-profile.md | 13 +- src/server/docs/users/get-my-settings.md | 14 +- src/server/docs/users/get-profile.md | 13 +- src/server/docs/users/update-my-settings.md | 12 + src/server/package-lock.json | 41 ++- src/server/tests/api/auth/auth-flow.test.js | 64 ++-- .../api/auth/send-verification-email.test.js | 44 +-- .../tests/api/photos/confirm-upload.test.js | 26 +- .../tests/api/photos/delete-photo.test.js | 30 +- src/server/tests/api/photos/get-photo.test.js | 16 +- .../tests/api/photos/reorder-photos.test.js | 22 +- src/server/tests/api/photos/sign-url.test.js | 40 ++- .../tests/api/relationships/block.test.js | 14 +- .../api/relationships/get-matches.test.js | 27 +- .../get-scene-candidates.test.js | 42 +-- .../tests/api/relationships/judge.test.js | 30 +- ...test.js => finalize-profile-setup.test.js} | 39 ++- .../tests/api/users/get-my-photos.test.js | 6 +- .../tests/api/users/get-my-profile.test.js | 14 +- .../tests/api/users/get-my-settings.test.js | 28 +- .../tests/api/users/get-profile.test.js | 34 +- .../tests/api/users/update-my-profile.test.js | 26 +- .../api/users/update-my-settings.test.js | 26 +- src/server/tests/utils/db.js | 8 +- 60 files changed, 908 insertions(+), 773 deletions(-) rename src/server/api/users/{create-my-profile.js => finalize-profile-setup.js} (62%) create mode 100644 src/server/db/migrations/1549211629961_remove-splash-photo.js rename src/server/docs/users/{create-my-profile.md => finalize-profile-setup.md} (70%) rename src/server/tests/api/users/{create-my-profile.test.js => finalize-profile-setup.test.js} (81%) diff --git a/src/server/api/auth/get-token-utln.js b/src/server/api/auth/get-token-utln.js index 79cc9d9d..ed01d714 100644 --- a/src/server/api/auth/get-token-utln.js +++ b/src/server/api/auth/get-token-utln.js @@ -13,8 +13,7 @@ const getTokenUtln = async (utln: string) => { // If the token is invalid, it will get caught upstream in the `authorized` // middleware. If it is valid, the request should include a `user` property // including the user's utln, which we will return here. - return apiUtils.status(200).json({ - status: codes.AUTHORIZED, + return apiUtils.status(codes.AUTHORIZED).data({ utln, }); }; diff --git a/src/server/api/auth/send-verification-email.js b/src/server/api/auth/send-verification-email.js index 7889390f..74aa299b 100644 --- a/src/server/api/auth/send-verification-email.js +++ b/src/server/api/auth/send-verification-email.js @@ -55,8 +55,7 @@ const sendVerificationEmail = async (utln: string, forceResend: boolean) => { // respond that the email has already been sent if (forceResend !== true && !oldCodeExpired) { logger.info(`Already sent code: ${code.code}`); - return apiUtils.status(200).json({ - status: codes.SEND_VERIFICATION_EMAIL__EMAIL_ALREADY_SENT, + return apiUtils.status(codes.SEND_VERIFICATION_EMAIL__EMAIL_ALREADY_SENT).data({ email: code.email, }); } @@ -67,22 +66,17 @@ const sendVerificationEmail = async (utln: string, forceResend: boolean) => { // If the member info is null (not found), error that it was not found. if (!memberInfo) { - return apiUtils.status(400).json({ - status: codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_FOUND, - }); + return apiUtils.status(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_FOUND).noData(); } // Ensure the member is a student if (!memberInfo.classYear) { - return apiUtils.status(400).json({ - status: codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_STUDENT, - }); + return apiUtils.status(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_STUDENT).noData(); } // Check that the student is in A&S or E if (!_.includes(['A&S', 'E'], memberInfo.college)) { - return apiUtils.status(400).json({ - status: codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_UNDERGRAD, + return apiUtils.status(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_UNDERGRAD).data({ college: memberInfo.college, classYear: memberInfo.classYear, }); @@ -90,8 +84,7 @@ const sendVerificationEmail = async (utln: string, forceResend: boolean) => { // Ensure user is in the Class of 2019 if (memberInfo.classYear !== '19') { - return apiUtils.status(400).json({ - status: codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_2019, + return apiUtils.status(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_2019).data({ classYear: memberInfo.classYear, }); } @@ -132,8 +125,7 @@ const sendVerificationEmail = async (utln: string, forceResend: boolean) => { slack.postVerificationCode(verificationCode, utln, memberInfo.email); // Send a success response to the client - return apiUtils.status(200).json({ - status: codes.SEND_VERIFICATION_EMAIL__SUCCESS, + return apiUtils.status(codes.SEND_VERIFICATION_EMAIL__SUCCESS).data({ email: memberInfo.email, }); }; diff --git a/src/server/api/auth/verify.js b/src/server/api/auth/verify.js index 78370ecb..f07339c6 100644 --- a/src/server/api/auth/verify.js +++ b/src/server/api/auth/verify.js @@ -42,9 +42,7 @@ const verify = async (utln: string, code: number) => { // No email has been sent for this utln if (result.rowCount === 0) { - return apiUtils.status(401).json({ - status: codes.VERIFY__NO_EMAIL_SENT, - }); + return apiUtils.status(codes.VERIFY__NO_EMAIL_SENT).noData(); } const verification = result.rows[0]; @@ -52,16 +50,12 @@ const verify = async (utln: string, code: number) => { // Check if the code is expired if (verification.attempts > 3 || expired) { - return apiUtils.status(400).json({ - status: codes.VERIFY__EXPIRED_CODE, - }); + return apiUtils.status(codes.VERIFY__EXPIRED_CODE).noData(); } // Check if the code is valid. If not, send a bad code message if (verification.code !== code) { - return apiUtils.status(400).json({ - status: codes.VERIFY__BAD_CODE, - }); + return apiUtils.status(codes.VERIFY__BAD_CODE).noData(); } // Success! The code is verified! @@ -97,8 +91,7 @@ const verify = async (utln: string, code: number) => { }); // Send the response back! - return apiUtils.status(200).json({ - status: codes.VERIFY__SUCCESS, + return apiUtils.status(codes.VERIFY__SUCCESS).data({ token, }); }; diff --git a/src/server/api/photos/confirm-upload.js b/src/server/api/photos/confirm-upload.js index 91ae04c6..41989236 100644 --- a/src/server/api/photos/confirm-upload.js +++ b/src/server/api/photos/confirm-upload.js @@ -29,9 +29,7 @@ const confirmUpload = async (userId: number) => { `, [userId]); if (unconfirmedPhotoRes.rowCount === 0) { - return apiUtils.status(400).json({ - status: codes.CONFIRM_UPLOAD__NO_UNCONFIRMED_PHOTO, - }); + return apiUtils.status(codes.CONFIRM_UPLOAD__NO_UNCONFIRMED_PHOTO).noData(); } const [{ uuid }] = unconfirmedPhotoRes.rows; @@ -47,9 +45,7 @@ const confirmUpload = async (userId: number) => { try { await s3.headObject(s3Params).promise(); } catch (error) { - return apiUtils.status(400).json({ - status: codes.CONFIRM_UPLOAD__NO_UPLOAD_FOUND, - }); + return apiUtils.status(codes.CONFIRM_UPLOAD__NO_UPLOAD_FOUND).noData(); } // TRANSACTION: Insert the photo and delete its "unconfirmed" counterpart @@ -71,20 +67,33 @@ const confirmUpload = async (userId: number) => { // Ensure there are only 3 or fewer photos const [{ photoCount }] = photosRes.rows; if (photoCount > 3) { - return apiUtils.status(400).json({ - status: codes.CONFIRM_UPLOAD__NO_AVAILABLE_SLOT, - }); + return apiUtils.status(codes.CONFIRM_UPLOAD__NO_AVAILABLE_SLOT).noData(); } // Insert the photo in the `photos` table, giving it the "next" index. const insertRes = await client.query(` - INSERT INTO photos - (user_id, index, uuid) - VALUES ($1, $2, $3) - RETURNING id - `, [userId, photoCount + 1, uuid]); - - const photoId = insertRes.rows[0].id; + WITH inserted AS ( + INSERT INTO photos + (user_id, index, uuid) + VALUES ($1, $2, $3) + RETURNING id + ) + SELECT + array_cat( + ARRAY( + SELECT id + FROM photos + WHERE user_id = $4 + ORDER BY index + ), + ARRAY( + SELECT id + FROM inserted + ) + ) AS "photoIds" + `, [userId, photoCount + 1, uuid, userId]); + + const [{ photoIds }] = insertRes.rows; // Delete the unconfirmed photo await client.query(` @@ -96,10 +105,7 @@ const confirmUpload = async (userId: number) => { await client.query('COMMIT'); client.release(); - return apiUtils.status(200).json({ - status: codes.CONFIRM_UPLOAD__SUCCESS, - photoId, - }); + return apiUtils.status(codes.CONFIRM_UPLOAD__SUCCESS).data(photoIds); } catch (err) { // Rollback the transaction and release the client await client.query('ROLLBACK'); diff --git a/src/server/api/photos/delete-photo.js b/src/server/api/photos/delete-photo.js index 7f8f1dc7..ecc0ec59 100644 --- a/src/server/api/photos/delete-photo.js +++ b/src/server/api/photos/delete-photo.js @@ -20,7 +20,7 @@ const bucket = config.get('s3_bucket'); * @api {delete} /api/photos/:photoId * */ -const deletePhoto = async (photoId: number, userId: number, userHasProfile: boolean) => { +const deletePhoto = async (photoId: number, userId: number) => { // On error, return a server error. const photosRes = await db.query(` SELECT id, uuid, index @@ -34,15 +34,7 @@ const deletePhoto = async (photoId: number, userId: number, userHasProfile: bool const photos = photosRes.rows; const [photoToDelete] = _.remove(photos, photo => photo.id === photoId); if (photoToDelete === undefined) { - return apiUtils.status(400).json({ - status: codes.DELETE_PHOTO__NOT_FOUND, - }); - } - - if (photos.length === 0) { - return apiUtils.status(409).json({ - status: codes.DELETE_PHOTO__CANNOT_DELETE_LAST_PHOTO, - }); + return apiUtils.status(codes.DELETE_PHOTO__NOT_FOUND).noData(); } // Transaction to delete the photo: @@ -51,20 +43,11 @@ const deletePhoto = async (photoId: number, userId: number, userHasProfile: bool // 0. Begin the transaction await client.query('BEGIN'); - // If we are deleting the splash photo, update the user's splash photo - // Only do this if the user already has a profile - if (userHasProfile && photoToDelete.index === 1) { - await client.query(` - UPDATE profiles - SET splash_photo_id = $1 - WHERE user_id = $2 - `, [photos[0].id, userId]); - } - // 1. Remove the photo from our database await client.query(` DELETE FROM photos - WHERE id = $1 + WHERE + id = $1 `, [photoToDelete.id]); // Get an updated list of photos for the requesting user @@ -72,16 +55,18 @@ const deletePhoto = async (photoId: number, userId: number, userHasProfile: bool return `(${photo.id}, ${index + 1})`; }); - // 2. Update the photos for the requesting user - await client.query(` - UPDATE photos - SET index = updated_photos.index - FROM - (VALUES - ${updatedPhotos.join(',')} - ) AS updated_photos (id, index) - WHERE photos.id = updated_photos.id - `); + // 2. Update the photos for the requesting user. Only do if some photos exist + if (photos.length > 0) { + await client.query(` + UPDATE photos + SET index = updated_photos.index + FROM + (VALUES + ${updatedPhotos.join(',')} + ) AS updated_photos (id, index) + WHERE photos.id = updated_photos.id + `); + } // 3. Delete the photo from S3 const params = { @@ -92,29 +77,21 @@ const deletePhoto = async (photoId: number, userId: number, userHasProfile: bool // 4. Commit the transaction! await client.query('COMMIT'); + + return apiUtils.status(codes.DELETE_PHOTO__SUCCESS).data( + _.map(photos, photo => photo.id), + ); } catch (err) { await client.query('ROLLBACK'); throw err; } finally { client.release(); } - - const newOrderRes = await db.query(` - SELECT id - FROM photos - WHERE user_id = $1 - ORDER BY index - `, [userId]); - - return apiUtils.status(200).json({ - status: codes.DELETE_PHOTO__SUCCESS, - photos: _.map(newOrderRes.rows, row => row.id), - }); }; const handler = [ apiUtils.asyncHandler(async (req: $Request) => { - return deletePhoto(Number.parseInt(req.params.photoId, 10), req.user.id, req.user.hasProfile); + return deletePhoto(Number.parseInt(req.params.photoId, 10), req.user.id); }), ]; diff --git a/src/server/api/photos/get-photo.js b/src/server/api/photos/get-photo.js index 0d5f6815..f5693a97 100644 --- a/src/server/api/photos/get-photo.js +++ b/src/server/api/photos/get-photo.js @@ -15,7 +15,7 @@ const NODE_ENV = serverUtils.getNodeEnv(); const s3 = new aws.S3({ region: 'us-east-1', signatureVersion: 'v4' }); const bucket = config.get('s3_bucket'); -const getSignedUrl = async (params) => { +const getSignedUrl = async (params): Promise => { return new Promise((resolve, reject) => { s3.getSignedUrl('getObject', params, (err, url) => { if (err) return reject(err); @@ -38,9 +38,11 @@ const getPhoto = async (photoId: number) => { // If it does not exist, error. if (photoRes.rowCount === 0) { - return apiUtils.status(400).json({ - status: codes.GET_PHOTO__NOT_FOUND, - }); + // Weird flowtype issue requires us to specifically define return type + // Same bug as https://github.com/facebook/flow/issues/5294. Not resolve. + // Should look into this more: Max made a trello ticket 2/4/19 + // $FlowFixMe + return apiUtils.status(codes.GET_PHOTO__NOT_FOUND).noData(); } // Sign a url for the photo and redirect the request to it @@ -49,9 +51,9 @@ const getPhoto = async (photoId: number) => { Bucket: bucket, Key: `photos/${NODE_ENV}/${uuid}`, }; + const url = await getSignedUrl(params); - return apiUtils.status(200).json({ - status: codes.GET_PHOTO__SUCCESS, + return apiUtils.status(codes.GET_PHOTO__SUCCESS).data({ url, }); }; @@ -62,10 +64,15 @@ const handler = [ async (req: $Request, res: $Response, next: $Next) => { try { const photoRes = await getPhoto(req.params.photoId); - if (photoRes.body.status === codes.GET_PHOTO__SUCCESS) { - return res.redirect(photoRes.body.url); + + // If the photo was succesfully retrieved, redirect to it! + // NOTE: This is "abnormal" by the standards of the API + if (photoRes.body.status === codes.GET_PHOTO__SUCCESS.status) { + return res.redirect(photoRes.body.data.url); } - return res.status(photoRes.status).json(photoRes.body); + + // On failure, respond 'normally' + return res.status(photoRes.statusCode).json(photoRes.body); } catch (err) { return next(err); } diff --git a/src/server/api/photos/reorder-photos.js b/src/server/api/photos/reorder-photos.js index 17479bbb..a5d3be1b 100644 --- a/src/server/api/photos/reorder-photos.js +++ b/src/server/api/photos/reorder-photos.js @@ -25,7 +25,7 @@ const schema = { * @api {patch} /api/photos/reorder * */ -const reorderPhotos = async (newOrder: number[], userId: number, userHasProfile: boolean) => { +const reorderPhotos = async (newOrder: number[], userId: number) => { // No worry about SQL Injection here: newOrder is verified to be an // array of integers/numbers. const result = await db.query(` @@ -41,9 +41,7 @@ const reorderPhotos = async (newOrder: number[], userId: number, userHasProfile: // If there are photo id mismatches, error if (mismatchCount > 0) { - return apiUtils.status(400).json({ - status: codes.REORDER_PHOTOS__MISMATCHED_IDS, - }); + return apiUtils.status(codes.REORDER_PHOTOS__MISMATCHED_IDS).noData(); } // Get an updated list of photos for the requesting user @@ -62,25 +60,13 @@ const reorderPhotos = async (newOrder: number[], userId: number, userHasProfile: WHERE photos.id = updated_photos.id `); - // If the user has a profile, set the new first photo to be the splash photo - if (userHasProfile) { - await db.query(` - UPDATE profiles - SET splash_photo_id = $1 - WHERE user_id = $2 - `, [newOrder[0], userId]); - } - - return apiUtils.status(200).json({ - status: codes.REORDER_PHOTOS__SUCCESS, - photos: newOrder, - }); + return apiUtils.status(codes.REORDER_PHOTOS__SUCCESS).data(newOrder); }; const handler = [ apiUtils.validate(schema), apiUtils.asyncHandler(async (req: $Request) => { - return reorderPhotos(req.body, req.user.id, req.user.hasProfile); + return reorderPhotos(req.body, req.user.id); }), ]; diff --git a/src/server/api/photos/sign-url.js b/src/server/api/photos/sign-url.js index 75f756ca..bffcd232 100644 --- a/src/server/api/photos/sign-url.js +++ b/src/server/api/photos/sign-url.js @@ -67,10 +67,7 @@ const signURL = async (userId: number) => { payload.fields.acl = 'authenticated-read'; // Return success! - return apiUtils.status(200).json({ - status: codes.SIGN_URL__SUCCESS, - payload, - }); + return apiUtils.status(codes.SIGN_URL__SUCCESS).data(payload); }; const handler = [ diff --git a/src/server/api/relationships/block.js b/src/server/api/relationships/block.js index 5751b58c..282a28ae 100644 --- a/src/server/api/relationships/block.js +++ b/src/server/api/relationships/block.js @@ -46,17 +46,13 @@ const block = async (userId: number, blockedUserId: number) => { `, [userId, blockedUserId]); // If the query succeeded, return success - return apiUtils.status(200).json({ - status: codes.BLOCK__SUCCESS, - }); + return apiUtils.status(codes.BLOCK__SUCCESS).noData(); } catch (err) { // If the query failed due to a voilation of the candidate_user_id fkey // into the profiles table, return a more specific error. See here: // https://www.postgresql.org/docs/10/errcodes-appendix.html if (err.code === '23503' && err.constraint === 'relationships_candidate_user_id_fkey') { - return apiUtils.status(400).json({ - status: codes.BLOCK__USER_NOT_FOUND, - }); + return apiUtils.status(codes.BLOCK__USER_NOT_FOUND).noData(); } throw err; diff --git a/src/server/api/relationships/get-matches.js b/src/server/api/relationships/get-matches.js index bdf501b8..1c439981 100644 --- a/src/server/api/relationships/get-matches.js +++ b/src/server/api/relationships/get-matches.js @@ -4,6 +4,7 @@ import type { $Request } from 'express'; const db = require('../../db'); const apiUtils = require('../utils'); +const { profileSelectQuery } = require('../users/utils'); const utils = require('./utils'); const codes = require('../status-codes'); @@ -42,9 +43,7 @@ const getMatches = async (userId: number) => { const result = await db.query(` SELECT they_profile.user_id as "userId", - they_profile.display_name AS "displayName", - to_char(they_profile.birthday, 'YYYY-MM-DD') AS birthday, - they_profile.bio, + ${profileSelectQuery('they_profile.user_id', { tableAlias: 'they_profile', buildJSON: true })} AS profile, array_remove(ARRAY[ ${matchedScenesSelect.join(',')} ], NULL) AS scenes @@ -62,10 +61,7 @@ const getMatches = async (userId: number) => { (${matchedScenesChecks.join(' OR ')}) `); - return apiUtils.status(200).json({ - status: codes.GET_MATCHES__SUCCESS, - matches: result.rows, - }); + return apiUtils.status(codes.GET_MATCHES__SUCCESS).data(result.rows); }; const handler = [ diff --git a/src/server/api/relationships/get-scene-candidates.js b/src/server/api/relationships/get-scene-candidates.js index b32166b0..76a7fda2 100644 --- a/src/server/api/relationships/get-scene-candidates.js +++ b/src/server/api/relationships/get-scene-candidates.js @@ -7,6 +7,7 @@ const _ = require('lodash'); const db = require('../../db'); const apiUtils = require('../utils'); const utils = require('./utils'); +const { profileSelectQuery } = require('../users/utils'); const codes = require('../status-codes'); /** @@ -20,8 +21,7 @@ const getSceneCandidates = async (userId: number, scene: string, exclude: number if (exclude) { // Ensure the exclude params are an array if (!Array.isArray(exclude)) { - return apiUtils.status(400).json({ - status: codes.BAD_REQUEST, + return apiUtils.status(codes.BAD_REQUEST).data({ message: 'Exclude paramaters recieved as a non-array. Use "exclude[]=..."', }); } @@ -31,8 +31,7 @@ const getSceneCandidates = async (userId: number, scene: string, exclude: number // Ensure all excluded users are integers. If not, error. if (_.includes(excludedUsers, NaN)) { - return apiUtils.status(400).json({ - status: codes.BAD_REQUEST, + return apiUtils.status(codes.BAD_REQUEST).data({ message: 'Exclude parameters includes a non-integer', }); } @@ -40,9 +39,7 @@ const getSceneCandidates = async (userId: number, scene: string, exclude: number // Ensure the scene is valid. if (!utils.sceneIsValid(scene)) { - return apiUtils.status(400).json({ - status: codes.GET_SCENE_CANDIDATES__INVALID_SCENE, - }); + return apiUtils.status(codes.GET_SCENE_CANDIDATES__INVALID_SCENE).noData(); } const isSmash = scene === 'smash'; @@ -65,18 +62,16 @@ const getSceneCandidates = async (userId: number, scene: string, exclude: number // parameter in the request const result = await db.query(` SELECT - profile.user_id as "userId", - profile.display_name AS "displayName", - to_char(profile.birthday, 'YYYY-MM-DD') AS birthday, - profile.bio + profile.user_id AS "userId", + ${profileSelectQuery('profile.user_id', { tableAlias: 'profile', buildJSON: true })} AS profile FROM profiles profile JOIN users candidate on candidate.id = profile.user_id - LEFT JOIN relationships r_critic ON r_critic.critic_user_id = ${userId} AND r_critic.candidate_user_id = candidate.id - LEFT JOIN relationships r_candidate ON r_candidate.critic_user_id = candidate.id AND r_candidate.candidate_user_id = ${userId} - ${isSmash ? `JOIN users critic on critic.id = ${userId}` : ''} + LEFT JOIN relationships r_critic ON r_critic.critic_user_id = $1 AND r_critic.candidate_user_id = candidate.id + LEFT JOIN relationships r_candidate ON r_candidate.critic_user_id = candidate.id AND r_candidate.candidate_user_id = $1 + ${isSmash ? 'JOIN users critic on critic.id = $1' : ''} WHERE - NOT profile.user_id = ANY($1) AND - profile.user_id != ${userId} AND + NOT profile.user_id = ANY($2) AND + profile.user_id != $1 AND candidate.active_${scene} AND NOT COALESCE(r_critic.blocked, false) AND NOT COALESCE(r_candidate.blocked, false) AND @@ -88,12 +83,11 @@ const getSceneCandidates = async (userId: number, scene: string, exclude: number )` : ''} ORDER BY r_critic.last_swipe_timestamp DESC NULLS FIRST LIMIT 10 - `, [excludedUsers]); - return apiUtils.status(200).json({ - status: codes.GET_SCENE_CANDIDATES__SUCCESS, - candidates: result.rows, - }); + + `, [userId, excludedUsers]); + + return apiUtils.status(codes.GET_SCENE_CANDIDATES__SUCCESS).data(result.rows); }; const handler = [ diff --git a/src/server/api/relationships/judge.js b/src/server/api/relationships/judge.js index 822e29ab..e8ab0fd8 100644 --- a/src/server/api/relationships/judge.js +++ b/src/server/api/relationships/judge.js @@ -48,22 +48,18 @@ const judge = async (userId: number, scene: string, candidateUserId: number, lik ON CONFLICT (critic_user_id, candidate_user_id) DO UPDATE SET - liked_${scene} = $4, + liked_${scene} = $3, last_swipe_timestamp = now() - `, [userId, candidateUserId, liked, liked]); + `, [userId, candidateUserId, liked]); // If the query succeeded, return success - return apiUtils.status(200).json({ - status: codes.JUDGE__SUCCESS, - }); + return apiUtils.status(codes.JUDGE__SUCCESS).noData(); } catch (err) { // If the query failed due to a voilation of the candidate_user_id fkey // into the profiles table, return a more specific error. See here: // https://www.postgresql.org/docs/10/errcodes-appendix.html if (err.code === '23503' && err.constraint === 'relationships_candidate_user_id_fkey') { - return apiUtils.status(400).json({ - status: codes.JUDGE__CANDIDATE_NOT_FOUND, - }); + return apiUtils.status(codes.JUDGE__CANDIDATE_NOT_FOUND).noData(); } throw err; diff --git a/src/server/api/status-codes.js b/src/server/api/status-codes.js index e3e94ace..f3e5da10 100644 --- a/src/server/api/status-codes.js +++ b/src/server/api/status-codes.js @@ -1,173 +1,221 @@ // @flow // SHARED -const SERVER_ERROR = 'SERVER_ERROR'; -const BAD_REQUEST = 'BAD_REQUEST'; -const AUTHORIZED = 'AUTHORIZED'; -const UNAUTHORIZED = 'UNAUTHORIZED'; -const PROFILE_SETUP_INCOMPLETE = 'PROFILE_SETUP_INCOMPLETE'; +exports.SERVER_ERROR = { + status: 'SERVER_ERROR', + code: 500, +}; +exports.BAD_REQUEST = { + status: 'BAD_REQUEST', + code: 400, +}; +exports.AUTHORIZED = { + status: 'AUTHORIZED', + code: 200, +}; +exports.UNAUTHORIZED = { + status: 'UNAUTHORIZED', + code: 401, +}; +exports.PROFILE_SETUP_INCOMPLETE = { + status: 'PROFILE_SETUP_INCOMPLETE', + code: 403, +}; // AUTH // Send Verification Email -const SEND_VERIFICATION_EMAIL__SUCCESS = 'SEND_VERIFICATION_EMAIL__SUCCESS'; -const SEND_VERIFICATION_EMAIL__UTLN_NOT_FOUND = 'SEND_VERIFICATION_EMAIL__UTLN_NOT_FOUND'; -const SEND_VERIFICATION_EMAIL__UTLN_NOT_2019 = 'SEND_VERIFICATION_EMAIL__UTLN_NOT_2019'; -const SEND_VERIFICATION_EMAIL__UTLN_NOT_STUDENT = 'SEND_VERIFICATION_EMAIL__UTLN_NOT_STUDENT'; -const SEND_VERIFICATION_EMAIL__UTLN_NOT_UNDERGRAD = 'SEND_VERIFICATION_EMAIL__UTLN_NOT_UNDERGRAD'; -const SEND_VERIFICATION_EMAIL__EMAIL_ALREADY_SENT = 'SEND_VERIFICATION_EMAIL__EMAIL_ALREADY_SENT'; +exports.SEND_VERIFICATION_EMAIL__SUCCESS = { + status: 'SEND_VERIFICATION_EMAIL__SUCCESS', + code: 200, +}; +exports.SEND_VERIFICATION_EMAIL__UTLN_NOT_FOUND = { + status: 'SEND_VERIFICATION_EMAIL__UTLN_NOT_FOUND', + code: 400, +}; +exports.SEND_VERIFICATION_EMAIL__UTLN_NOT_2019 = { + status: 'SEND_VERIFICATION_EMAIL__UTLN_NOT_2019', + code: 400, +}; +exports.SEND_VERIFICATION_EMAIL__UTLN_NOT_STUDENT = { + status: 'SEND_VERIFICATION_EMAIL__UTLN_NOT_STUDENT', + code: 400, +}; +exports.SEND_VERIFICATION_EMAIL__UTLN_NOT_UNDERGRAD = { + status: 'SEND_VERIFICATION_EMAIL__UTLN_NOT_UNDERGRAD', + code: 400, +}; +exports.SEND_VERIFICATION_EMAIL__EMAIL_ALREADY_SENT = { + status: 'SEND_VERIFICATION_EMAIL__EMAIL_ALREADY_SENT', + code: 200, +}; // Verify -const VERIFY__SUCCESS = 'VERIFY__SUCCESS'; -const VERIFY__BAD_CODE = 'VERIFY__BAD_CODE'; -const VERIFY__EXPIRED_CODE = 'VERIFY__EXPIRED_CODE'; -const VERIFY__NO_EMAIL_SENT = 'VERIFY__NO_EMAIL_SENT'; +exports.VERIFY__SUCCESS = { + status: 'VERIFY__SUCCESS', + code: 200, +}; +exports.VERIFY__BAD_CODE = { + status: 'VERIFY__BAD_CODE', + code: 400, +}; +exports.VERIFY__EXPIRED_CODE = { + status: 'VERIFY__EXPIRED_CODE', + code: 400, +}; +exports.VERIFY__NO_EMAIL_SENT = { + status: 'VERIFY__NO_EMAIL_SENT', + code: 400, +}; // USERS // Create Profile -const CREATE_PROFILE__SUCCESS = 'CREATE_PROFILE__SUCCESS'; -const CREATE_PROFILE__PROFILE_ALREADY_CREATED = 'CREATE_PROFILE__PROFILE_ALREADY_CREATED'; -const CREATE_PROFILE__INVALID_REQUEST = 'CREATE_PROFILE__INVALID_REQUEST'; -const CREATE_PROFILE__PHOTO_REQUIRED = 'CREATE_PROFILE__PHOTO_REQUIRED'; +exports.FINALIZE_PROFILE_SETUP__SUCCESS = { + status: 'FINALIZE_PROFILE_SETUP__SUCCESS', + code: 201, +}; +exports.FINALIZE_PROFILE_SETUP__PROFILE_ALREADY_CREATED = { + status: 'FINALIZE_PROFILE_SETUP__PROFILE_ALREADY_CREATED', + code: 409, +}; +exports.FINALIZE_PROFILE_SETUP__INVALID_REQUEST = { + status: 'FINALIZE_PROFILE_SETUP__INVALID_REQUEST', + code: 400, +}; +exports.FINALIZE_PROFILE_SETUP__PHOTO_REQUIRED = { + status: 'FINALIZE_PROFILE_SETUP__PHOTO_REQUIRED', + code: 409, +}; // Update Profile -const UPDATE_PROFILE__SUCCESS = 'UPDATE_PROFILE__SUCCESS'; -const UPDATE_PROFILE__INVALID_REQUEST = 'UPDATE_PROFILE__INVALID_REQUEST'; +exports.UPDATE_PROFILE__SUCCESS = { + status: 'UPDATE_PROFILE__SUCCESS', + code: 201, +}; +exports.UPDATE_PROFILE__INVALID_REQUEST = { + status: 'UPDATE_PROFILE__INVALID_REQUEST', + code: 400, +}; // Get profile -const GET_PROFILE__SUCCESS = 'GET_PROFILE__SUCCESS'; -const GET_PROFILE__PROFILE_NOT_FOUND = 'GET_PROFILE__PROFILE_NOT_FOUND'; -const GET_PROFILE__BAD_USER_ID = 'GET_PROFILE__BAD_USER_ID'; +exports.GET_PROFILE__SUCCESS = { + status: 'GET_PROFILE__SUCCESS', + code: 200, +}; +exports.GET_PROFILE__PROFILE_NOT_FOUND = { + status: 'GET_PROFILE__PROFILE_NOT_FOUND', + code: 404, +}; +exports.GET_PROFILE__BAD_USER_ID = { + status: 'GET_PROFILE__BAD_USER_ID', + code: 400, +}; // Get My Photos -const GET_MY_PHOTOS__SUCCESS = 'GET_MY_PHOTOS__SUCCESS'; +exports.GET_MY_PHOTOS__SUCCESS = { + status: 'GET_MY_PHOTOS__SUCCESS', + code: 200, +}; // Get Scene Candidates -const GET_SCENE_CANDIDATES__SUCCESS = 'GET_SCENE_CANDIDATES__SUCCESS'; -const GET_SCENE_CANDIDATES__INVALID_SCENE = 'GET_SCENE_CANDIDATES__INVALID_SCENE'; +exports.GET_SCENE_CANDIDATES__SUCCESS = { + status: 'GET_SCENE_CANDIDATES__SUCCESS', + code: 200, +}; +exports.GET_SCENE_CANDIDATES__INVALID_SCENE = { + status: 'GET_SCENE_CANDIDATES__INVALID_SCENE', + code: 400, +}; // Get Matches -const GET_MATCHES__SUCCESS = 'GET_MATCHES__SUCCESS'; +exports.GET_MATCHES__SUCCESS = { + status: 'GET_MATCHES__SUCCESS', + code: 200, +}; // Judge -const JUDGE__SUCCESS = 'JUDGE__SUCCESS'; -const JUDGE__CANDIDATE_NOT_FOUND = 'JUDGE__CANDIDATE_NOT_FOUND'; +exports.JUDGE__SUCCESS = { + status: 'JUDGE__SUCCESS', + code: 200, +}; +exports.JUDGE__CANDIDATE_NOT_FOUND = { + status: 'JUDGE__CANDIDATE_NOT_FOUND', + code: 400, +}; // Block -const BLOCK__SUCCESS = 'BLOCK__SUCCESS'; -const BLOCK__USER_NOT_FOUND = 'BLOCK__USER_NOT_FOUND'; +exports.BLOCK__SUCCESS = { + status: 'BLOCK__SUCCESS', + code: 200, +}; +exports.BLOCK__USER_NOT_FOUND = { + status: 'BLOCK__USER_NOT_FOUND', + code: 400, +}; // Update Settings -const UPDATE_SETTINGS__SUCCESS = 'UPDATE_SETTINGS__SUCCESS'; +exports.UPDATE_SETTINGS__SUCCESS = { + status: 'UPDATE_SETTINGS__SUCCESS', + code: 201, +}; // Get Settings -const GET_SETTINGS__SUCCESS = 'GET_SETTINGS__SUCCESS'; +exports.GET_SETTINGS__SUCCESS = { + status: 'GET_SETTINGS__SUCCESS', + code: 200, +}; // PHOTOS // Sign Url -const SIGN_URL__SUCCESS = 'SIGN_URL__SUCCESS'; +exports.SIGN_URL__SUCCESS = { + status: 'SIGN_URL__SUCCESS', + code: 200, +}; // Get Photo -const GET_PHOTO__SUCCESS = 'GET_PHOTO__SUCCESS'; -const GET_PHOTO__NOT_FOUND = 'GET_PHOTO__NOT_FOUND'; +exports.GET_PHOTO__SUCCESS = { + status: 'GET_PHOTO__SUCCESS', + code: 200, +}; +exports.GET_PHOTO__NOT_FOUND = { + status: 'GET_PHOTO__NOT_FOUND', + code: 400, +}; // Confirm Upload -const CONFIRM_UPLOAD__SUCCESS = 'CONFIRM_UPLOAD__SUCCESS'; -const CONFIRM_UPLOAD__NO_UNCONFIRMED_PHOTO = 'CONFIRM_UPLOAD__NO_UNCONFIRMED_PHOTO'; -const CONFIRM_UPLOAD__NO_UPLOAD_FOUND = 'CONFIRM_UPLOAD__NO_UPLOAD_FOUND'; -const CONFIRM_UPLOAD__NO_AVAILABLE_SLOT = 'CONFIRM_UPLOAD__NO_AVAILABLE_SLOT'; +exports.CONFIRM_UPLOAD__SUCCESS = { + status: 'CONFIRM_UPLOAD__SUCCESS', + code: 200, +}; +exports.CONFIRM_UPLOAD__NO_UNCONFIRMED_PHOTO = { + status: 'CONFIRM_UPLOAD__NO_UNCONFIRMED_PHOTO', + code: 400, +}; +exports.CONFIRM_UPLOAD__NO_UPLOAD_FOUND = { + status: 'CONFIRM_UPLOAD__NO_UPLOAD_FOUND', + code: 400, +}; +exports.CONFIRM_UPLOAD__NO_AVAILABLE_SLOT = { + status: 'CONFIRM_UPLOAD__NO_AVAILABLE_SLOT', + code: 400, +}; // Delete Photo -const DELETE_PHOTO__SUCCESS = 'DELETE_PHOTO__SUCCESS'; -const DELETE_PHOTO__CANNOT_DELETE_LAST_PHOTO = 'DELETE_PHOTO__CANNOT_DELETE_LAST_PHOTO'; -const DELETE_PHOTO__NOT_FOUND = 'DELETE_PHOTO__NOT_FOUND'; +exports.DELETE_PHOTO__SUCCESS = { + status: 'DELETE_PHOTO__SUCCESS', + code: 200, +}; +exports.DELETE_PHOTO__NOT_FOUND = { + status: 'DELETE_PHOTO__NOT_FOUND', + code: 400, +}; // Reorder Photos -const REORDER_PHOTOS__SUCCESS = 'REORDER_PHOTOS__SUCCESS'; -const REORDER_PHOTOS__MISMATCHED_IDS = 'REORDER_PHOTOS__MISMATCHED_IDS'; - -module.exports = { - // Shared - SERVER_ERROR, - BAD_REQUEST, - AUTHORIZED, - UNAUTHORIZED, - PROFILE_SETUP_INCOMPLETE, - // AUTH - // Send Verification Email - SEND_VERIFICATION_EMAIL__SUCCESS, - SEND_VERIFICATION_EMAIL__UTLN_NOT_FOUND, - SEND_VERIFICATION_EMAIL__UTLN_NOT_2019, - SEND_VERIFICATION_EMAIL__UTLN_NOT_UNDERGRAD, - SEND_VERIFICATION_EMAIL__UTLN_NOT_STUDENT, - SEND_VERIFICATION_EMAIL__EMAIL_ALREADY_SENT, - - // Verify - VERIFY__SUCCESS, - VERIFY__BAD_CODE, - VERIFY__EXPIRED_CODE, - VERIFY__NO_EMAIL_SENT, - - // USERS - // Create Profile - CREATE_PROFILE__SUCCESS, - CREATE_PROFILE__PROFILE_ALREADY_CREATED, - CREATE_PROFILE__INVALID_REQUEST, - CREATE_PROFILE__PHOTO_REQUIRED, - - // Update Profile - UPDATE_PROFILE__SUCCESS, - UPDATE_PROFILE__INVALID_REQUEST, - - // Get Profile - GET_PROFILE__SUCCESS, - GET_PROFILE__PROFILE_NOT_FOUND, - GET_PROFILE__BAD_USER_ID, - - // Get My Photos - GET_MY_PHOTOS__SUCCESS, - - // RELATIONSHIPS - // Get Scene Candidates - GET_SCENE_CANDIDATES__SUCCESS, - GET_SCENE_CANDIDATES__INVALID_SCENE, - - // Judge - JUDGE__SUCCESS, - JUDGE__CANDIDATE_NOT_FOUND, - - // Block - BLOCK__SUCCESS, - BLOCK__USER_NOT_FOUND, - - // Get Matches - GET_MATCHES__SUCCESS, - // Update Settings - UPDATE_SETTINGS__SUCCESS, - - // Get Settings - GET_SETTINGS__SUCCESS, - - // PHOTOS - // Sign Url - SIGN_URL__SUCCESS, - - // Get Photo - GET_PHOTO__SUCCESS, - GET_PHOTO__NOT_FOUND, - - // Confirm Upload - CONFIRM_UPLOAD__SUCCESS, - CONFIRM_UPLOAD__NO_UNCONFIRMED_PHOTO, - CONFIRM_UPLOAD__NO_UPLOAD_FOUND, - CONFIRM_UPLOAD__NO_AVAILABLE_SLOT, - - // Delete Photo - DELETE_PHOTO__SUCCESS, - DELETE_PHOTO__CANNOT_DELETE_LAST_PHOTO, - DELETE_PHOTO__NOT_FOUND, - - // Reorder Photos - REORDER_PHOTOS__SUCCESS, - REORDER_PHOTOS__MISMATCHED_IDS, +exports.REORDER_PHOTOS__SUCCESS = { + status: 'REORDER_PHOTOS__SUCCESS', + code: 200, +}; +exports.REORDER_PHOTOS__MISMATCHED_IDS = { + status: 'REORDER_PHOTOS__MISMATCHED_IDS', + code: 400, }; diff --git a/src/server/api/users/create-my-profile.js b/src/server/api/users/finalize-profile-setup.js similarity index 62% rename from src/server/api/users/create-my-profile.js rename to src/server/api/users/finalize-profile-setup.js index dd93b4b0..607baaa1 100644 --- a/src/server/api/users/create-my-profile.js +++ b/src/server/api/users/finalize-profile-setup.js @@ -3,7 +3,7 @@ import type { $Request } from 'express'; const apiUtils = require('../utils'); -const utils = require('./utils'); +const { validateProfile, profileSelectQuery } = require('./utils'); const codes = require('../status-codes'); const db = require('../../db'); @@ -37,10 +37,9 @@ const createMyProfile = async (userId: number, profile: Object) => { // Validate the profile. If validate profile throws, there was a problem with // the given profile, which means it was a bad request try { - utils.validateProfile(profile); + validateProfile(profile); } catch (error) { - return apiUtils.status(400).json({ - status: codes.CREATE_PROFILE__INVALID_REQUEST, + return apiUtils.status(codes.FINALIZE_PROFILE_SETUP__INVALID_REQUEST).data({ message: error, }); } @@ -50,7 +49,7 @@ const createMyProfile = async (userId: number, profile: Object) => { birthday, bio, } = profile; - // Get the user's splash photo ID. Error if it does not exist. + // Ensure that the user has a photo uploaded. Error if it does not exist. const photoResult = await db.query(` SELECT id FROM photos @@ -60,34 +59,29 @@ const createMyProfile = async (userId: number, profile: Object) => { `, [userId]); if (photoResult.rowCount === 0) { - return apiUtils.status(409).json({ - status: codes.CREATE_PROFILE__PHOTO_REQUIRED, - }); + return apiUtils.status(codes.FINALIZE_PROFILE_SETUP__PHOTO_REQUIRED).noData(); } - const splashPhotoId = photoResult.rows[0].id; - // Insert the profile into the database const results = await db.query(` INSERT INTO profiles - (user_id, display_name, birthday, bio, splash_photo_id) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT DO NOTHING - RETURNING user_id AS "userId" + (user_id, display_name, birthday, bio) + VALUES ($1, $2, $3, $4) + ON CONFLICT (user_id) DO UPDATE + SET display_name = EXCLUDED.display_name + RETURNING + ${profileSelectQuery('$5')}, + (xmax::text::int > 0) as existed `, - [userId, displayName, birthday, bio, splashPhotoId]); + [userId, displayName, birthday, bio, userId]); - // If no rows were returned, then the profile already exists. - if (results.rowCount === 0) { - return apiUtils.status(409).json({ - status: codes.CREATE_PROFILE__PROFILE_ALREADY_CREATED, - }); - } + const [{ existed, ...finalizedProfile }] = results.rows; - // If there is an id returned, success! - return apiUtils.status(201).json({ - status: codes.CREATE_PROFILE__SUCCESS, - }); + return apiUtils.status( + existed + ? codes.FINALIZE_PROFILE_SETUP__PROFILE_ALREADY_CREATED + : codes.FINALIZE_PROFILE_SETUP__SUCCESS, + ).data(finalizedProfile); }; const handler = [ diff --git a/src/server/api/users/get-my-photos.js b/src/server/api/users/get-my-photos.js index c5a0d0e3..31da6581 100644 --- a/src/server/api/users/get-my-photos.js +++ b/src/server/api/users/get-my-photos.js @@ -20,10 +20,9 @@ const getMyPhotos = async (userId: number) => { ORDER BY index `, [userId]); - return apiUtils.status(200).json({ - status: codes.GET_MY_PHOTOS__SUCCESS, - photoIds: _.map(result.rows, row => row.id), - }); + return apiUtils.status(codes.GET_MY_PHOTOS__SUCCESS).data( + _.map(result.rows, row => row.id), + ); }; const handler = [ diff --git a/src/server/api/users/get-my-settings.js b/src/server/api/users/get-my-settings.js index da767869..9b13d836 100644 --- a/src/server/api/users/get-my-settings.js +++ b/src/server/api/users/get-my-settings.js @@ -3,9 +3,12 @@ import type { $Request } from 'express'; const apiUtils = require('../utils'); +const { settingsSelectQuery } = require('./utils'); const codes = require('../status-codes'); const db = require('../../db'); +const selectSettings = settingsSelectQuery(); + /** * @api {get} /api/users/me/settings * @@ -13,33 +16,12 @@ const db = require('../../db'); const getMySettings = async (userId: number) => { // try to get the user's settings from the users table const result = await db.query(` - SELECT - want_he as "wantHe", - want_she as "wantShe", - want_they as "wantThey", - use_he as "useHe", - use_she as "useShe", - use_they as "useThey" + SELECT ${selectSettings} FROM users WHERE id = $1`, [userId]); // Can assume user exists and is in db b/c authenticated upstream - const settings = result.rows[0]; - return apiUtils.status(200).json({ - status: codes.GET_SETTINGS__SUCCESS, - settings: { - usePronouns: { - he: settings.useHe, - she: settings.useShe, - they: settings.useThey, - }, - wantPronouns: { - he: settings.wantHe, - she: settings.wantShe, - they: settings.wantThey, - }, - }, - }); + return apiUtils.status(codes.GET_SETTINGS__SUCCESS).data(result.rows[0]); }; const handler = [ diff --git a/src/server/api/users/get-profile.js b/src/server/api/users/get-profile.js index b94aec0a..dca607f8 100644 --- a/src/server/api/users/get-profile.js +++ b/src/server/api/users/get-profile.js @@ -2,9 +2,8 @@ import type { $Request } from 'express'; -const _ = require('lodash'); - const db = require('../../db'); +const { profileSelectQuery } = require('./utils'); const apiUtils = require('../utils'); const codes = require('../status-codes'); @@ -23,44 +22,23 @@ const schema = { const getProfile = async (userId: number) => { // Get if the user id is a valid integer. If not, error with a bad request if (Number.isNaN(userId)) { - return apiUtils.status(400).json({ - status: codes.GET_PROFILE__BAD_USER_ID, - }); + return apiUtils.status(codes.GET_PROFILE__BAD_USER_ID).noData(); } // Try to get the user from the profiles table const result = await db.query(` - SELECT - display_name as "displayName", - birthday, - bio + SELECT ${profileSelectQuery('$1')} FROM profiles - WHERE user_id = $1`, [userId]); + WHERE user_id = $1 + `, [userId]); // If the user is not in the database, respond with 'not found' if (result.rowCount === 0) { - return apiUtils.status(404).json({ - status: codes.GET_PROFILE__PROFILE_NOT_FOUND, - }); + return apiUtils.status(codes.GET_PROFILE__PROFILE_NOT_FOUND).noData(); } - const profile = result.rows[0]; - profile.birthday = profile.birthday.toISOString().substring(0, 10); - - const photosRes = await db.query(` - SELECT id - FROM photos - WHERE user_id = $1 - ORDER BY index - `, [userId]); - - profile.photos = _.map(photosRes.rows, row => row.id); - // If the profile was found, return it! - return apiUtils.status(200).json({ - status: codes.GET_PROFILE__SUCCESS, - profile, - }); + return apiUtils.status(codes.GET_PROFILE__SUCCESS).data(result.rows[0]); }; const handler = [ diff --git a/src/server/api/users/index.js b/src/server/api/users/index.js index 39e5dea4..bc9ef2a6 100644 --- a/src/server/api/users/index.js +++ b/src/server/api/users/index.js @@ -2,7 +2,7 @@ const express = require('express'); -const createMyProfile = require('./create-my-profile'); +const finalizeProfileSetup = require('./finalize-profile-setup'); const getMySettings = require('./get-my-settings'); const updateMySettings = require('./update-my-settings'); const getMyProfile = require('./get-my-profile'); @@ -15,7 +15,7 @@ const { hasProfile } = require('../utils').middleware; const usersRouter = express.Router(); // AUTHENTICATED METHODS -usersRouter.post('/me/profile', createMyProfile.handler); +usersRouter.post('/me/profile', finalizeProfileSetup.handler); usersRouter.get('/me/settings', getMySettings.handler); usersRouter.patch('/me/settings', updateMySettings.handler); usersRouter.get('/me/photos', getMyPhotos.handler); diff --git a/src/server/api/users/update-my-profile.js b/src/server/api/users/update-my-profile.js index 626a2082..e0462d7e 100644 --- a/src/server/api/users/update-my-profile.js +++ b/src/server/api/users/update-my-profile.js @@ -4,7 +4,7 @@ import type { $Request } from 'express'; const _ = require('lodash'); -const utils = require('./utils'); +const { validateProfile, profileSelectQuery, getFieldTemplates } = require('./utils'); const apiUtils = require('../utils'); const codes = require('../status-codes'); const db = require('../../db'); @@ -31,6 +31,8 @@ const schema = { }; /* eslint-enable */ +const definedSelect = profileSelectQuery('$1'); + /** * @api {patch} /api/users/me/profile * @@ -39,10 +41,9 @@ const updateMyProfile = async (userId: number, profile: Object) => { // Validate the profile. If validate profile throws, there was a problem with // the given profile, which means it was a bad request try { - utils.validateProfile(profile); + validateProfile(profile); } catch (error) { - return apiUtils.status(400).json({ - status: codes.UPDATE_PROFILE__INVALID_REQUEST, + return apiUtils.status(codes.UPDATE_PROFILE__INVALID_REQUEST).data({ message: error, }); } @@ -60,29 +61,33 @@ const updateMyProfile = async (userId: number, profile: Object) => { // for a consistant ordering const definedFields = _.toPairs(_.omitBy(allFields, _.isUndefined)); + let result; + // If there is nothing to update, success! if (definedFields.length === 0) { - return apiUtils.status(201).json({ - status: codes.UPDATE_PROFILE__SUCCESS, - }); - } - - // Generates a template and fields for a postgres query - const template = utils.getFieldTemplates(definedFields); + result = await db.query(` + SELECT ${definedSelect} + FROM profiles + WHERE user_id = $1 + `, [userId]); + } else { + // Generates a template and fields for a postgres query + const template = getFieldTemplates(definedFields); - // Update the profile in the database. Utilize fieldTemplates and the field - // length as the parameter templates. It is ok to construct the string like - // this because none of the values in the construction come from user input - await db.query(` - UPDATE profiles - SET ${template.templateString} - WHERE user_id = $${template.fields.length + 1}`, - [...template.fields, userId]); + // Update the profile in the database. Utilize fieldTemplates and the field + // length as the parameter templates. It is ok to construct the string like + // this because none of the values in the construction come from user input + const userParamIndex = template.fields.length + 1; + result = await db.query(` + UPDATE profiles + SET ${template.templateString} + WHERE user_id = $${userParamIndex} + RETURNING ${profileSelectQuery(`${userParamIndex}`)} + `, [...template.fields, userId]); + } // If there is an id returned, success! - return apiUtils.status(201).json({ - status: codes.UPDATE_PROFILE__SUCCESS, - }); + return apiUtils.status(codes.UPDATE_PROFILE__SUCCESS).data(result.rows[0]); }; const handler = [ diff --git a/src/server/api/users/update-my-settings.js b/src/server/api/users/update-my-settings.js index 334282b3..a4264277 100644 --- a/src/server/api/users/update-my-settings.js +++ b/src/server/api/users/update-my-settings.js @@ -5,7 +5,7 @@ import type { $Request } from 'express'; const _ = require('lodash'); const apiUtils = require('../utils'); -const utils = require('./utils'); +const { settingsSelectQuery, getFieldTemplates } = require('./utils'); const codes = require('../status-codes'); const db = require('../../db'); @@ -69,29 +69,33 @@ const updateMySettings = async (userId: number, wantPronouns: Object, usePronoun // for a consistant ordering const definedFields = _.toPairs(_.omitBy(allFields, _.isUndefined)); - // If there is nothing to update, success! - if (definedFields.length === 0) { - return apiUtils.status(201).json({ - status: codes.UPDATE_SETTINGS__SUCCESS, - }); - } + // Result of SELECT or UPDATE query (will be the same either way) + let result; - // Get an object of the template strings and fields - const fieldTemplate = utils.getFieldTemplates(definedFields); + // If there is nothing to update, just get the settings and return them + if (definedFields.length === 0) { + result = await db.query(` + SELECT ${settingsSelectQuery()} + FROM users + WHERE id = $1 + `, [userId]); + } else { + // Get an object of the template strings and fields + const fieldTemplate = getFieldTemplates(definedFields); - // Update the settings in the database. Utilize fieldTemplates and the field - // length as the parameter templates. It is ok to construct the string like - // this because none of the values in the construction come from user input - await db.query(` - UPDATE users - SET ${fieldTemplate.templateString} - WHERE id = $${fieldTemplate.fields.length + 1}`, - [...fieldTemplate.fields, userId]); + // Update the settings in the database. Utilize fieldTemplates and the field + // length as the parameter templates. It is ok to construct the string like + // this because none of the values in the construction come from user input + result = await db.query(` + UPDATE users + SET ${fieldTemplate.templateString} + WHERE id = $${fieldTemplate.fields.length + 1} + RETURNING ${settingsSelectQuery()} + `, [...fieldTemplate.fields, userId]); + } // If there is an id returned, success! - return apiUtils.status(201).json({ - status: codes.UPDATE_SETTINGS__SUCCESS, - }); + return apiUtils.status(codes.UPDATE_SETTINGS__SUCCESS).data(result.rows[0]); }; const handler = [ diff --git a/src/server/api/users/utils.js b/src/server/api/users/utils.js index c58d12e2..909370dd 100644 --- a/src/server/api/users/utils.js +++ b/src/server/api/users/utils.js @@ -75,8 +75,88 @@ function getFieldTemplates(definedFields: Array<[string, any]>) { }; } +const DefaultProfileOptions = { + tableAlias: '', + buildJSON: false, +}; + +/* +This function defines the select statement for the profile fields. +It allows some options: + tableAlias: the name of the alias for the table in the query. Example: 'they_profile' + buildJSON: instead of returning teh fields directly, this builds the entire profile + into a JSON object which can then be named and returned as desired. + See "get-scene-candidates.js" for an example of this +*/ +function profileSelectQuery( + userIdMatch: string, /* The query paramater or other match for the user's profile to get. + e.g: $1 or they_profile.user_id */ + options: typeof DefaultProfileOptions = DefaultProfileOptions, // See options above +) { + const opts = { + ...DefaultProfileOptions, + ...options, + }; + const tableName = opts.tableAlias === '' ? '' : `${opts.tableAlias}.`; + + const fields = ` + json_build_object( + 'displayName', ${tableName}display_name, + 'birthday', to_char(${tableName}birthday, 'YYYY-MM-DD'), + 'bio', ${tableName}bio + ) + `; + + const photoIds = ` + ARRAY( + SELECT id + FROM photos + WHERE user_id = ${userIdMatch} + ORDER BY index + ) + `; + + if (opts.buildJSON) { + return ` + json_build_object( + 'fields', ${fields}, + 'photoIds', ${photoIds} + ) + `; + } + + return ` + ${fields} AS fields, + ${photoIds} AS "photoIds" + `; +} + +/* +This function allows the selection of a user's settings in a reusable way! Use it in a SELECT +or RETURNING statement. + - settingsTableAlias: the alias for the settings table for the user. E.g. user_setttings +*/ +function settingsSelectQuery(settingsTableAlias: string = '') { + const tableName = settingsTableAlias === '' ? '' : `${settingsTableAlias}.`; + + return ` + json_build_object( + 'he', ${tableName}want_he, + 'she', ${tableName}want_she, + 'they', ${tableName}want_they + ) AS "wantPronouns", + json_build_object( + 'he', ${tableName}use_he, + 'she', ${tableName}use_she, + 'they', ${tableName}use_they + ) AS "usePronouns" + `; +} + module.exports = { validateProfile, profileErrorMessages, getFieldTemplates, + profileSelectQuery, + settingsSelectQuery, }; diff --git a/src/server/api/utils/async-handler.js b/src/server/api/utils/async-handler.js index 7f1fbc6b..26b9b3bd 100644 --- a/src/server/api/utils/async-handler.js +++ b/src/server/api/utils/async-handler.js @@ -11,8 +11,12 @@ const asyncHandler = (fn : Middleware) => { return (req: $Request, res: $Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)) .then((response) => { - res.status(response.status).json(response.body); + // This is where the magic happens + // Given the promise resolves, return the response's status and the body + res.status(response.statusCode).json(response.body); }) + // If the promise fails, call the next middleware with the error + // this will go to the error handler in /api/index.js .catch(next); }; }; diff --git a/src/server/api/utils/middleware/authenticated.js b/src/server/api/utils/middleware/authenticated.js index 5fd7ea60..4c2753ff 100644 --- a/src/server/api/utils/middleware/authenticated.js +++ b/src/server/api/utils/middleware/authenticated.js @@ -53,7 +53,7 @@ const authenticated = async (req: $Request, res: $Response, next: $Next) => { const token = req.get('Authorization'); if (token === undefined) { return res.status(400).json({ - status: codes.BAD_REQUEST, + status: codes.BAD_REQUEST.status, message: 'Missing Authorization header.', }); } @@ -70,7 +70,7 @@ const authenticated = async (req: $Request, res: $Response, next: $Next) => { // return with UNAUTHORIZED } catch (error) { return res.status(401).json({ - status: codes.UNAUTHORIZED, + status: codes.UNAUTHORIZED.status, }); } }; diff --git a/src/server/api/utils/middleware/hasProfile.js b/src/server/api/utils/middleware/hasProfile.js index 2b3de8df..29bc3861 100644 --- a/src/server/api/utils/middleware/hasProfile.js +++ b/src/server/api/utils/middleware/hasProfile.js @@ -11,7 +11,7 @@ const hasProfile = async (req: $Request, res: $Response, next: $Next) => { // table yet, which implies that they are not onboarded. if (!req.user.hasProfile) { return res.status(403).json({ - status: codes.PROFILE_SETUP_INCOMPLETE, + status: codes.PROFILE_SETUP_INCOMPLETE.status, }); } diff --git a/src/server/api/utils/status.js b/src/server/api/utils/status.js index 6ea602ca..2993fb9b 100644 --- a/src/server/api/utils/status.js +++ b/src/server/api/utils/status.js @@ -1,11 +1,43 @@ // @flow -const status = (statusCode: number) => { +type ResponseStatus = { + status: string, + code: number, +}; + +export type DataResponse = { + statusCode: number, + body: { + status: string, + data: T, + }, +}; + +export type NoDataResponse = { + statusCode: number, + body: { + status: string, + }, +}; + + +const status = (responseStatus: ResponseStatus) => { return { - json: (body: Object) => { + data: function data(responseData: T): DataResponse { + return { + statusCode: responseStatus.code, + body: { + status: responseStatus.status, + data: responseData, + }, + }; + }, + noData: (): NoDataResponse => { return { - status: statusCode, - body, + statusCode: responseStatus.code, + body: { + status: responseStatus.status, + }, }; }, }; diff --git a/src/server/api/utils/validate.js b/src/server/api/utils/validate.js index c96759ae..58319551 100644 --- a/src/server/api/utils/validate.js +++ b/src/server/api/utils/validate.js @@ -21,7 +21,7 @@ const validate = (schema: Object) => { } return res.status(400).json({ - status: codes.BAD_REQUEST, + status: codes.BAD_REQUEST.status, message: ajv.errorsText(validateSchema.errors), }); }; diff --git a/src/server/db/migrations/1549211629961_remove-splash-photo.js b/src/server/db/migrations/1549211629961_remove-splash-photo.js new file mode 100644 index 00000000..2ce7d092 --- /dev/null +++ b/src/server/db/migrations/1549211629961_remove-splash-photo.js @@ -0,0 +1,17 @@ +exports.shorthands = undefined; + +exports.up = (pgm) => { + pgm.dropColumns('profiles', ['splash_photo_id']); +}; + +exports.down = (pgm) => { + pgm.addColumns('profiles', { + splash_photo_id: { + type: 'int', + unique: true, + }, + }); + pgm.addConstraint('profiles', 'profiles_photo_exists', ` + foreign key (user_id, splash_photo_id) references photos (user_id, id) on delete restrict + `); +}; diff --git a/src/server/db/pool.js b/src/server/db/pool.js index d9f4ec7e..c2a683bf 100644 --- a/src/server/db/pool.js +++ b/src/server/db/pool.js @@ -5,6 +5,6 @@ const { Pool } = require('pg'); const pool = new Pool(config.get('db')); module.exports = { - query: (text: string, params: ?(any[])) => pool.query(text, params), + query: (text: string, params: ?(any[])): Promise => pool.query(text, params), connect: async () => pool.connect(), }; diff --git a/src/server/docs/photos/confirm-upload.md b/src/server/docs/photos/confirm-upload.md index a4bd1232..4ff01418 100644 --- a/src/server/docs/photos/confirm-upload.md +++ b/src/server/docs/photos/confirm-upload.md @@ -21,7 +21,7 @@ Provide the normal `Authorization` token in the request header. ## Success Response -**Condition**: An unconfirmed photo exists AND a corresponding photo has been successfully been uploaded to AWS S3. In this case, the uploaded photo will be added to the requesting user's confirmed photos. +**Condition**: An unconfirmed photo exists AND a corresponding photo has been successfully been uploaded to AWS S3. In this case, the uploaded photo will be added to the requesting user's confirmed photos. The data is the new list if photoIds for the user **Code**: `200 OK` @@ -29,7 +29,8 @@ Provide the normal `Authorization` token in the request header. ```json { - "status": "CONFIRM_UPLOAD__SUCCESS" + "status": "CONFIRM_UPLOAD__SUCCESS", + "data": [1, 2, 3] } ``` diff --git a/src/server/docs/photos/delete-photo.md b/src/server/docs/photos/delete-photo.md index 986bd3bf..7a217feb 100644 --- a/src/server/docs/photos/delete-photo.md +++ b/src/server/docs/photos/delete-photo.md @@ -21,7 +21,7 @@ Provide the normal `Authorization` token in the request header. ## Success Response -**Condition**: The photo with the given id exists. The photo will be deleted. +**Condition**: The photo with the given id exists. The photo will be deleted. The return data is the updated list of photo ids. **Code**: `200 OK` @@ -30,7 +30,7 @@ Provide the normal `Authorization` token in the request header. ```json { "status": "DELETE_PHOTO__SUCCESS", - "photos": [1, 2, 3] + "data": [1, 2, 3] } ``` @@ -47,17 +47,3 @@ Provide the normal `Authorization` token in the request header. "status": "DELETE_PHOTO__NOT_FOUND" } ``` - -### OR - -**Condition** : The photo that was supplied is the only photo the user currently has uploaded. Because users need at least one photo to have a profile, we reject the deleton of this photo. That being said, the photo does exist. - -**Code** : `409 CONFLICT` - -**Content** - -```json -{ - "status": "DELETE_PHOTO__CANNOT_DELETE_LAST_PHOTO" -} -``` diff --git a/src/server/docs/photos/reorder-photos.md b/src/server/docs/photos/reorder-photos.md index 68b64240..e8240cc7 100644 --- a/src/server/docs/photos/reorder-photos.md +++ b/src/server/docs/photos/reorder-photos.md @@ -1,4 +1,6 @@ -# Reorder Photos +# Reorder Photos - DEPRICATED + +NOTE: This endpoint is currently depricated. If needed, we will update it with a better return value and a more transaction-like implentation. Reorder the photos of the current user. Provide between 2 and 4 ids in an array. @@ -46,7 +48,7 @@ Type: `Array of Integers` ```json { "status": "REORDER_PHOTOS__SUCCESS", - "photos": [1, 2, 3] + "data": [1, 2, 3] } ``` diff --git a/src/server/docs/photos/sign-url.md b/src/server/docs/photos/sign-url.md index f61d8a17..ddc08fff 100644 --- a/src/server/docs/photos/sign-url.md +++ b/src/server/docs/photos/sign-url.md @@ -28,16 +28,16 @@ Provide the normal `Authorization` token in the request header. ```json { "status": "SIGN_URL__SUCCESS", - "payload": { + "data": { "url": "https://s3.amazonaws.com/projectgem-dev", "fields": { "key": "photos/development/43a0fa39-bfd1-4afe-a5ab-f2a130219eb2", "bucket": "projectgem-dev", "X-Amz-Algorithm": "AWS4-HMAC-SHA256", - "X-Amz-Credential": "AKIAIFDBBAE2RB3D6GQA/20190120/us-east-1/s3/aws4_request", - "X-Amz-Date": "20190120T011631Z", - "Policy": "eyJleHBpcmF0aW9uIjoiMjAxOS0wMS0yMFQwMToyNjozMVoiLCJjb25kaXRpb25zIjpbeyJhY2wiOiJhdXRoZW50aWNhdGVkLXJlYWQifSx7IkNvbnRlbnQtVHlwZSI6ImltYWdlL2pwZWcifSxbImNvbnRlbnQtbGVuZ3RoLXJhbmdlIiwxLDUwMDAwMF0seyJrZXkiOiJwaG90b3MvZGV2ZWxvcG1lbnQvNDNhMGZhMzktYmZkMS00YWZlLWE1YWItZjJhMTMwMjE5ZWIyIn0seyJidWNrZXQiOiJwcm9qZWN0Z2VtLWRldiJ9LHsiWC1BbXotQWxnb3JpdGhtIjoiQVdTNC1ITUFDLVNIQTI1NiJ9LHsiWC1BbXotQ3JlZGVudGlhbCI6IkFLSUFJRkRCQkFFMlJCM0Q2R1FBLzIwMTkwMTIwL3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QifSx7IlgtQW16LURhdGUiOiIyMDE5MDEyMFQwMTE2MzFaIn1dfQ==", - "X-Amz-Signature": "b6e2e0b39ba25cc2d639a023f18ed8dc1516f23e48e029010eccfd2060c012da", + "X-Amz-Credential": "AKIAIFDBBAE2RB3D6GQA/20190202/us-east-1/s3/aws4_request", + "X-Amz-Date": "20190202T230311Z", + "Policy": "eyJleHBpcmF0aW9uIjoiMjAxOS0wMi0wMlQyMzoxMzoxMVoiLCJjb25kaXRpb25zIjpbeyJhY2wiOiJhdXRoZW50aWNhdGVkLXJlYWQifSx7IkNvbnRlbnQtVHlwZSI6ImltYWdlL2pwZWcifSxbImNvbnRlbnQtbGVuZ3RoLXJhbmdlIiwxLDUwMDAwMF0seyJrZXkiOiJwaG90b3MvZGV2ZWxvcG1lbnQvNDNhMGZhMzktYmZkMS00YWZlLWE1YWItZjJhMTMwMjE5ZWIyIn0seyJidWNrZXQiOiJwcm9qZWN0Z2VtLWRldiJ9LHsiWC1BbXotQWxnb3JpdGhtIjoiQVdTNC1ITUFDLVNIQTI1NiJ9LHsiWC1BbXotQ3JlZGVudGlhbCI6IkFLSUFJRkRCQkFFMlJCM0Q2R1FBLzIwMTkwMjAyL3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QifSx7IlgtQW16LURhdGUiOiIyMDE5MDIwMlQyMzAzMTFaIn1dfQ==", + "X-Amz-Signature": "2f742f6cd47f2943d8128089bfce730823763d43145ba5d77ef7e8f1e2ee38fc", "acl": "authenticated-read" } } diff --git a/src/server/docs/relationships/get-matches.md b/src/server/docs/relationships/get-matches.md index 242edf4c..b5c6ae73 100644 --- a/src/server/docs/relationships/get-matches.md +++ b/src/server/docs/relationships/get-matches.md @@ -35,21 +35,31 @@ Provide the normal `Authorization` token in the request header. ```json { "status": "GET_MATCHES__SUCCESS", - "matches": [ + "data": [ { "userId": 6, - "displayName": "Emily", - "birthday": "1997-10-10", - "bio": "Cool", + "profile": { + "fields": { + "displayName": "Emily", + "birthday": "1997-10-10", + "bio": "Cool" + }, + "photoIds": [1, 2, 3] + }, "scenes": [ "smash" ] }, { "userId": 8, - "displayName": "Jacob", - "birthday": "1997-10-10", - "bio": "Cool", + "profile": { + "fields": { + "displayName": "Jacob", + "birthday": "1997-10-10", + "bio": "Cool" + }, + "photoIds": [4, 5, 6] + }, "scenes": [ "social", "stone" diff --git a/src/server/docs/relationships/get-scene-candidates.md b/src/server/docs/relationships/get-scene-candidates.md index 7ecfb959..d150de55 100644 --- a/src/server/docs/relationships/get-scene-candidates.md +++ b/src/server/docs/relationships/get-scene-candidates.md @@ -46,18 +46,17 @@ Provide the normal `Authorization` token in the request header. ```json { "status": "GET_SCENE_CANDIDATES__SUCCESS", - "candidates": [ - { - "userId": 4, - "displayName": "Tony", - "birthday": "1996-11-13", - "bio": "The Real President", - }, + "data": [ { "userId": 2, - "displayName": "Monaco", - "birthday": "1997-08-12", - "bio": "Mr. President", + "profile": { + "fields": { + "displayName": "Anthony", + "birthday": "2019-02-19", + "bio": "He is the president" + }, + "photoIds": [1] + } } ] } diff --git a/src/server/docs/users/create-my-profile.md b/src/server/docs/users/finalize-profile-setup.md similarity index 70% rename from src/server/docs/users/create-my-profile.md rename to src/server/docs/users/finalize-profile-setup.md index 3c6c91b7..bf647828 100644 --- a/src/server/docs/users/create-my-profile.md +++ b/src/server/docs/users/finalize-profile-setup.md @@ -1,6 +1,6 @@ -# Create My Profile +# Finalize Profile setup -Create a profile for the current user. Only allow this if a user has not yet created a profile. A user must have a confirmed uploaded photo to create a profile. +Finalize the profile setup for the requesting user. Only allow this if a user has not yet created a profile. A user must have a confirmed uploaded photo to run this endpoint. The endpoint takes the profile "fields" and returns the finalized profile. **URL** : `/api/users/me/profile` @@ -47,7 +47,17 @@ Provide the user's initial profile fields. ```json { - "status": "CREATE_PROFILE__SUCCESS", + "status": "FINALIZE_PROFILE_SETUP__SUCCESS", + "data": { + "fields": { + "displayName": "Max", + "birthday": "1999-01-27", + "bio": "Already has 2 friends so..." + }, + "photoIds": [ + 1 + ] + } } ``` @@ -60,7 +70,7 @@ Provide the user's initial profile fields. **Content** : ```json { - "status": "CREATE_PROFILE__PROFILE_ALREADY_CREATED" + "status": "FINALIZE_PROFILE_SETUP__PROFILE_ALREADY_CREATED" } ``` @@ -73,7 +83,7 @@ Provide the user's initial profile fields. **Content** : ```json { - "status": "CREATE_PROFILE__INVALID_REQUEST", + "status": "FINALIZE_PROFILE_SETUP__INVALID_REQUEST", "message": "DISPLAY_NAME_TOO_LONG" } ``` @@ -87,7 +97,7 @@ Provide the user's initial profile fields. **Content** : ```json { - "status": "CREATE_PROFILE__INVALID_REQUEST", + "status": "FINALIZE_PROFILE_SETUP__INVALID_REQUEST", "message": "BIRTHDAY_NOT_VALID" } ``` @@ -101,7 +111,7 @@ Provide the user's initial profile fields. **Content** : ```json { - "status": "CREATE_PROFILE__INVALID_REQUEST", + "status": "FINALIZE_PROFILE_SETUP__INVALID_REQUEST", "message": "BIO_TOO_LONG" } ``` @@ -131,6 +141,6 @@ Provide the user's initial profile fields. ```json { - "status": "CREATE_PROFILE__PHOTO_REQUIRED" + "status": "FINALIZE_PROFILE_SETUP__PHOTO_REQUIRED" } ``` diff --git a/src/server/docs/users/get-my-photos.md b/src/server/docs/users/get-my-photos.md index e7ee8591..3dd2e906 100644 --- a/src/server/docs/users/get-my-photos.md +++ b/src/server/docs/users/get-my-photos.md @@ -29,7 +29,7 @@ Provide the normal `Authorization` token in the request header. ```json { "status": "GET_MY_PHOTOS__SUCCESS", - "photoIds": [ + "data": [ 14 ] } diff --git a/src/server/docs/users/get-my-profile.md b/src/server/docs/users/get-my-profile.md index ff021c3c..bee34c48 100644 --- a/src/server/docs/users/get-my-profile.md +++ b/src/server/docs/users/get-my-profile.md @@ -33,10 +33,15 @@ Provide the normal `Authorization` token in the request header. ```json { "status": "GET_PROFILE__SUCCESS", - "profile": { - "displayName": "Max Greenwald", - "birthday": "1997-10-10", - "bio": "Cool" + "data": { + "fields": { + "displayName": "Max", + "birthday": "1999-01-27", + "bio": "Already has 2 friends so..." + }, + "photoIds": [ + 14 + ] } } ``` diff --git a/src/server/docs/users/get-my-settings.md b/src/server/docs/users/get-my-settings.md index cfba0685..a8520a03 100644 --- a/src/server/docs/users/get-my-settings.md +++ b/src/server/docs/users/get-my-settings.md @@ -29,16 +29,16 @@ Provide the normal `Authorization` token in the request header. ```json { "status": "GET_SETTINGS__SUCCESS", - "settings": { + "data": { "usePronouns": { - "he": true, - "she": true, - "they": true + "he": false, + "she": false, + "they": false }, "wantPronouns": { - "he": true, - "she": true, - "they": true + "he": false, + "she": false, + "they": false } } } diff --git a/src/server/docs/users/get-profile.md b/src/server/docs/users/get-profile.md index 9b31e020..1cb64d37 100644 --- a/src/server/docs/users/get-profile.md +++ b/src/server/docs/users/get-profile.md @@ -36,10 +36,15 @@ Provide the normal `Authorization` token in the request header. ```json { "status": "GET_PROFILE__SUCCESS", - "profile": { - "displayName": "Max Greenwald", - "birthday": "1997-10-10", - "bio": "Cool" + "data": { + "fields": { + "displayName": "Max", + "birthday": "1999-01-27", + "bio": "Already has 2 friends so..." + }, + "photoIds": [ + 14 + ] } } ``` diff --git a/src/server/docs/users/update-my-settings.md b/src/server/docs/users/update-my-settings.md index 77a64c31..e975b1e3 100644 --- a/src/server/docs/users/update-my-settings.md +++ b/src/server/docs/users/update-my-settings.md @@ -75,6 +75,18 @@ Provide updated settings fields ```json { "status": "UPDATE_SETTINGS__SUCCESS", + "data": { + "wantPronouns": { + "he": false, + "she": true, + "they": false + }, + "usePronouns": { + "he": false, + "she": false, + "they": false + } + } } ``` diff --git a/src/server/package-lock.json b/src/server/package-lock.json index fe8f4bcb..9aac1b6c 100644 --- a/src/server/package-lock.json +++ b/src/server/package-lock.json @@ -3992,7 +3992,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -4013,12 +4014,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4033,17 +4036,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4160,7 +4166,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4172,6 +4179,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4186,6 +4194,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4193,12 +4202,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4217,6 +4228,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4297,7 +4309,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4309,6 +4322,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -4394,7 +4408,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -4430,6 +4445,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4449,6 +4465,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4492,12 +4509,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/src/server/tests/api/auth/auth-flow.test.js b/src/server/tests/api/auth/auth-flow.test.js index 49e3b637..162c10ff 100644 --- a/src/server/tests/api/auth/auth-flow.test.js +++ b/src/server/tests/api/auth/auth-flow.test.js @@ -30,8 +30,8 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(200); - expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__SUCCESS); - expect(res.body.email).toContain('Jasmin.Chun@tufts.edu'); + expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__SUCCESS.status); + expect(res.body.data.email).toContain('Jasmin.Chun@tufts.edu'); const codeForGoodUtln = await db.query('SELECT code FROM verification_codes WHERE utln = $1 LIMIT 1', [GOOD_UTLN]); return request(app) @@ -45,8 +45,8 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(200) .then((res2) => { - expect(res2.body.status).toBe(codes.VERIFY__SUCCESS); - expect(res2.body.token).toBeDefined(); + expect(res2.body.status).toEqual(codes.VERIFY__SUCCESS.status); + expect(res2.body.data.token).toBeDefined(); }); }); @@ -61,9 +61,9 @@ describe('api/auth/verify', () => { }, ) .set('Accept', 'application/json') - .expect(401) + .expect(400) .then((res) => { - expect(res.body.status).toBe(codes.VERIFY__NO_EMAIL_SENT); + expect(res.body.status).toBe(codes.VERIFY__NO_EMAIL_SENT.status); }); }); @@ -86,7 +86,7 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(400); - expect(req1.body.status).toBe(codes.VERIFY__BAD_CODE); + expect(req1.body.status).toEqual(codes.VERIFY__BAD_CODE.status); const req2 = await request(app) .post('/api/auth/verify') @@ -99,7 +99,7 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(400); - expect(req2.body.status).toBe(codes.VERIFY__BAD_CODE); + expect(req2.body.status).toEqual(codes.VERIFY__BAD_CODE.status); const req3 = await request(app) .post('/api/auth/verify') @@ -112,7 +112,7 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(400); - expect(req3.body.status).toBe(codes.VERIFY__BAD_CODE); + expect(req3.body.status).toEqual(codes.VERIFY__BAD_CODE.status); const codeForGoodUtln = await db.query('SELECT code FROM verification_codes WHERE utln = $1 LIMIT 1', [GOOD_UTLN]); return request(app) @@ -126,7 +126,7 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(400) .then((res) => { - expect(res.body.status).toBe(codes.VERIFY__EXPIRED_CODE); + expect(res.body.status).toBe(codes.VERIFY__EXPIRED_CODE.status); }); }); @@ -143,8 +143,8 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(200); - expect(sendEmailRes.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__SUCCESS); - expect(sendEmailRes.body.email).toContain('Ronald.Zampolin@tufts.edu'); + expect(sendEmailRes.body.status).toEqual(codes.SEND_VERIFICATION_EMAIL__SUCCESS.status); + expect(sendEmailRes.body.data.email).toContain('Ronald.Zampolin@tufts.edu'); const verifyRes1 = await request(app) .post('/api/auth/verify') @@ -157,7 +157,7 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(400); - expect(verifyRes1.body.status).toBe(codes.VERIFY__BAD_CODE); + expect(verifyRes1.body.status).toEqual(codes.VERIFY__BAD_CODE.status); const verifyRes2 = await request(app) .post('/api/auth/verify') @@ -170,7 +170,7 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(400); - expect(verifyRes2.body.status).toBe(codes.VERIFY__BAD_CODE); + expect(verifyRes2.body.status).toEqual(codes.VERIFY__BAD_CODE.status); const codeForGoodUtln = await db.query('SELECT code FROM verification_codes WHERE utln = $1 LIMIT 1', [GOOD_UTLN2]); return request(app) @@ -184,7 +184,7 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(200) .then((res) => { - expect(res.body.status).toBe(codes.VERIFY__SUCCESS); + expect(res.body.status).toBe(codes.VERIFY__SUCCESS.status); }); }); @@ -206,7 +206,7 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(400); - expect(res1.body.status).toBe(codes.VERIFY__BAD_CODE); + expect(res1.body.status).toEqual(codes.VERIFY__BAD_CODE.status); const codeForGoodUtln = await db.query('SELECT code FROM verification_codes WHERE utln = $1 LIMIT 1', [GOOD_UTLN2]); return request(app) @@ -220,8 +220,8 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(200) .then((res) => { - expect(res.body.status).toBe(codes.VERIFY__SUCCESS); - expect(res.body.token).toBeDefined(); + expect(res.body.status).toBe(codes.VERIFY__SUCCESS.status); + expect(res.body.data.token).toBeDefined(); }); }); @@ -239,8 +239,8 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(200); - expect(res1.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__SUCCESS); - expect(res1.body.email).toContain('Ronald.Zampolin@tufts.edu'); + expect(res1.body.status).toEqual(codes.SEND_VERIFICATION_EMAIL__SUCCESS.status); + expect(res1.body.data.email).toContain('Ronald.Zampolin@tufts.edu'); const codeForGoodUtln = await db.query('SELECT code FROM verification_codes WHERE utln = $1 LIMIT 1', [GOOD_UTLN2]); return request(app) @@ -254,8 +254,8 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(200) .then((res) => { - expect(res.body.status).toBe(codes.VERIFY__SUCCESS); - expect(res.body.token).toBeDefined(); + expect(res.body.status).toBe(codes.VERIFY__SUCCESS.status); + expect(res.body.data.token).toBeDefined(); }); }); @@ -271,8 +271,8 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(200); - expect(res1.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__SUCCESS); - expect(res1.body.email).toContain('Ronald.Zampolin@tufts.edu'); + expect(res1.body.status).toEqual(codes.SEND_VERIFICATION_EMAIL__SUCCESS.status); + expect(res1.body.data.email).toContain('Ronald.Zampolin@tufts.edu'); let codeForGoodUtln = await db.query('SELECT code FROM verification_codes WHERE utln = $1 LIMIT 1', [GOOD_UTLN2]); let res = await request(app) @@ -287,10 +287,10 @@ describe('api/auth/verify', () => { .expect(200); - const firstToken = res.body.token; + const firstToken = res.body.data.token; - expect(res.body.status).toBe(codes.VERIFY__SUCCESS); - expect(res.body.token).toBeDefined(); + expect(res.body.status).toBe(codes.VERIFY__SUCCESS.status); + expect(res.body.data.token).toBeDefined(); // Log in again res1 = await request(app) @@ -304,8 +304,8 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(200); - expect(res1.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__SUCCESS); - expect(res1.body.email).toContain('Ronald.Zampolin@tufts.edu'); + expect(res1.body.status).toEqual(codes.SEND_VERIFICATION_EMAIL__SUCCESS.status); + expect(res1.body.data.email).toContain('Ronald.Zampolin@tufts.edu'); codeForGoodUtln = await db.query('SELECT code FROM verification_codes WHERE utln = $1 LIMIT 1', [GOOD_UTLN2]); res = await request(app) @@ -319,8 +319,8 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json') .expect(200); - expect(res.body.status).toBe(codes.VERIFY__SUCCESS); - expect(res.body.token).toBeDefined(); + expect(res.body.status).toBe(codes.VERIFY__SUCCESS.status); + expect(res.body.data.token).toBeDefined(); // Ensure that the first token is invalidated res = await request(app) @@ -329,6 +329,6 @@ describe('api/auth/verify', () => { .set('Accept', 'application/json'); expect(res.statusCode).toBe(401); - expect(res.body.status).toBe(codes.UNAUTHORIZED); + expect(res.body.status).toBe(codes.UNAUTHORIZED.status); }); }); diff --git a/src/server/tests/api/auth/send-verification-email.test.js b/src/server/tests/api/auth/send-verification-email.test.js index 233a72b8..9bf491e0 100644 --- a/src/server/tests/api/auth/send-verification-email.test.js +++ b/src/server/tests/api/auth/send-verification-email.test.js @@ -36,7 +36,7 @@ describe('api/auth/send-verification-email', () => { .set('Accept', 'application/json') .expect(400) .then((res) => { - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toContain('utln'); }); }); @@ -52,7 +52,7 @@ describe('api/auth/send-verification-email', () => { .set('Accept', 'application/json') .expect(400) .then((res) => { - expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_FOUND); + expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_FOUND.status); }); }); @@ -66,8 +66,8 @@ describe('api/auth/send-verification-email', () => { ) .set('Accept', 'application/json') .expect(400); - expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_2019); - expect(res.body.classYear).toBe('20'); + expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_2019.status); + expect(res.body.data.classYear).toBe('20'); }); it('should fail given a utln that is not a current student', async () => { @@ -80,7 +80,7 @@ describe('api/auth/send-verification-email', () => { ) .set('Accept', 'application/json') .expect(400); - expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_STUDENT); + expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_STUDENT.status); }); it('should fail given a utln that is a current GRADUATE student in the class of 2019', async () => { @@ -93,9 +93,9 @@ describe('api/auth/send-verification-email', () => { ) .set('Accept', 'application/json') .expect(400); - expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_UNDERGRAD); - expect(res.body.college).toBeDefined(); - expect(res.body.classYear).toBe('19'); + expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_UNDERGRAD.status); + expect(res.body.data.college).toBeDefined(); + expect(res.body.data.classYear).toBe('19'); }); it('should succeed in sending an email with a valid utln', () => { @@ -109,10 +109,10 @@ describe('api/auth/send-verification-email', () => { .set('Accept', 'application/json') .expect(200) .then((res) => { - expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__SUCCESS); - expect(res.body.email).toBeDefined(); - expect(res.body.email).toMatch(/^[A-Za-z0-9._%+-]+@tufts.edu/); - expect(res.body.email).not.toContain(GOOD_UTLN); + expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__SUCCESS.status); + expect(res.body.data.email).toBeDefined(); + expect(res.body.data.email).toMatch(/^[A-Za-z0-9._%+-]+@tufts.edu/); + expect(res.body.data.email).not.toContain(GOOD_UTLN); }); }); @@ -127,10 +127,10 @@ describe('api/auth/send-verification-email', () => { .set('Accept', 'application/json') .expect(200) .then((res) => { - expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__EMAIL_ALREADY_SENT); - expect(res.body.email).toBeDefined(); - expect(res.body.email).toMatch(/^[A-Za-z0-9._%+-]+@tufts.edu/); - expect(res.body.email).not.toContain(GOOD_UTLN); + expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__EMAIL_ALREADY_SENT.status); + expect(res.body.data.email).toBeDefined(); + expect(res.body.data.email).toMatch(/^[A-Za-z0-9._%+-]+@tufts.edu/); + expect(res.body.data.email).not.toContain(GOOD_UTLN); }); }); @@ -146,10 +146,10 @@ describe('api/auth/send-verification-email', () => { .set('Accept', 'application/json') .expect(200) .then((res) => { - expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__SUCCESS); - expect(res.body.email).toBeDefined(); - expect(res.body.email).toMatch(/^[A-Za-z0-9._%+-]+@tufts.edu/); - expect(res.body.email).not.toContain(GOOD_UTLN); + expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__SUCCESS.status); + expect(res.body.data.email).toBeDefined(); + expect(res.body.data.email).toMatch(/^[A-Za-z0-9._%+-]+@tufts.edu/); + expect(res.body.data.email).not.toContain(GOOD_UTLN); }); }); @@ -165,7 +165,7 @@ describe('api/auth/send-verification-email', () => { .set('Accept', 'application/json') .expect(200) .then((res) => { - expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__EMAIL_ALREADY_SENT); + expect(res.body.status).toBe(codes.SEND_VERIFICATION_EMAIL__EMAIL_ALREADY_SENT.status); }); }); @@ -181,7 +181,7 @@ describe('api/auth/send-verification-email', () => { .set('Accept', 'application/json') .expect(400) .then((res) => { - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); }); }); }); diff --git a/src/server/tests/api/photos/confirm-upload.test.js b/src/server/tests/api/photos/confirm-upload.test.js index 5117a2e0..8a12b79d 100644 --- a/src/server/tests/api/photos/confirm-upload.test.js +++ b/src/server/tests/api/photos/confirm-upload.test.js @@ -37,7 +37,7 @@ describe('GET api/photos/confirm_upload', () => { .get('/api/photos/sign-url') .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); }); @@ -47,7 +47,7 @@ describe('GET api/photos/confirm_upload', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.CONFIRM_UPLOAD__NO_UNCONFIRMED_PHOTO); + expect(res.body.status).toBe(codes.CONFIRM_UPLOAD__NO_UNCONFIRMED_PHOTO.status); }); it('should fail if a photo was not actually uploaded', async () => { @@ -57,14 +57,14 @@ describe('GET api/photos/confirm_upload', () => { .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.SIGN_URL__SUCCESS); + expect(res.body.status).toBe(codes.SIGN_URL__SUCCESS.status); res = await request(app) .get('/api/photos/confirm-upload') .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.CONFIRM_UPLOAD__NO_UPLOAD_FOUND); + expect(res.body.status).toBe(codes.CONFIRM_UPLOAD__NO_UPLOAD_FOUND.status); }); it('should succeed if the photo was properly uploaded', async () => { @@ -74,19 +74,19 @@ describe('GET api/photos/confirm_upload', () => { .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.SIGN_URL__SUCCESS); + expect(res.body.status).toBe(codes.SIGN_URL__SUCCESS.status); // Perform the file upload - await utils.uploadTestPhoto(res.body.payload); - const { key } = res.body.payload.fields; + await utils.uploadTestPhoto(res.body.data); + const { key } = res.body.data.fields; res = await request(app) .get('/api/photos/confirm-upload') .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.CONFIRM_UPLOAD__SUCCESS); - expect(Number.isInteger(res.body.photoId) && res.body.photoId > 0).toBeTruthy(); + expect(res.body.status).toBe(codes.CONFIRM_UPLOAD__SUCCESS.status); + expect(Number.isInteger(res.body.data[0]) && res.body.data[0] > 0).toBeTruthy(); await utils.deletePhoto(key); }); @@ -107,19 +107,19 @@ describe('GET api/photos/confirm_upload', () => { .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.SIGN_URL__SUCCESS); + expect(res.body.status).toBe(codes.SIGN_URL__SUCCESS.status); // Perform the file upload - await utils.uploadTestPhoto(res.body.payload); + await utils.uploadTestPhoto(res.body.data); - const { key } = res.body.payload.fields; + const { key } = res.body.data.fields; res = await request(app) .get('/api/photos/confirm-upload') .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.CONFIRM_UPLOAD__NO_AVAILABLE_SLOT); + expect(res.body.status).toBe(codes.CONFIRM_UPLOAD__NO_AVAILABLE_SLOT.status); await utils.deletePhoto(key); }); diff --git a/src/server/tests/api/photos/delete-photo.test.js b/src/server/tests/api/photos/delete-photo.test.js index e1db391b..ac443302 100644 --- a/src/server/tests/api/photos/delete-photo.test.js +++ b/src/server/tests/api/photos/delete-photo.test.js @@ -35,7 +35,7 @@ describe('DELETE api/photos/:photoId', () => { .delete('/api/photos/1') .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); }); @@ -45,7 +45,7 @@ describe('DELETE api/photos/:photoId', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.DELETE_PHOTO__NOT_FOUND); + expect(res.body.status).toBe(codes.DELETE_PHOTO__NOT_FOUND.status); }); it('should fail if the photo belongs to another user', async () => { @@ -64,29 +64,18 @@ describe('DELETE api/photos/:photoId', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.DELETE_PHOTO__NOT_FOUND); + expect(res.body.status).toBe(codes.DELETE_PHOTO__NOT_FOUND.status); }); - it('should fail if the user only has one photo remaining', async () => { - const photoRes = await db.query(` + it('should succeed if the photo was properly deleted', async () => { + let photoRes = await db.query(` INSERT INTO photos (user_id, index, uuid) VALUES ($1, 1, $2) RETURNING id `, [me.id, uuidv4()]); - const [{ id }] = photoRes.rows; - - const res = await request(app) - .delete(`/api/photos/${id}`) - .set('Authorization', me.token) - .set('Accept', 'application/json'); - expect(res.statusCode).toBe(409); - expect(res.body.status).toBe(codes.DELETE_PHOTO__CANNOT_DELETE_LAST_PHOTO); - }); - - it('should succeed if the photo was properly uploaded', async () => { - const photoRes = await db.query(` + photoRes = await db.query(` INSERT INTO photos (user_id, index, uuid) VALUES ($1, 2, $2) @@ -100,7 +89,8 @@ describe('DELETE api/photos/:photoId', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.DELETE_PHOTO__SUCCESS); + expect(res.body.status).toBe(codes.DELETE_PHOTO__SUCCESS.status); + expect(res.body.data.length).toBe(1); }); it('should reorder photos upon deletion', async () => { @@ -125,7 +115,7 @@ describe('DELETE api/photos/:photoId', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.DELETE_PHOTO__SUCCESS); + expect(res.body.status).toBe(codes.DELETE_PHOTO__SUCCESS.status); photoRes = await db.query(` SELECT index, id @@ -135,6 +125,6 @@ describe('DELETE api/photos/:photoId', () => { `, [me.id]); expect(photoRes.rows[1].index).toBe(2); - expect(res.body.photos[0]).toBe(photoRes.rows[0].id); + expect(res.body.data[0]).toBe(photoRes.rows[0].id); }); }); diff --git a/src/server/tests/api/photos/get-photo.test.js b/src/server/tests/api/photos/get-photo.test.js index aedcd3b0..0b109c8f 100644 --- a/src/server/tests/api/photos/get-photo.test.js +++ b/src/server/tests/api/photos/get-photo.test.js @@ -34,7 +34,7 @@ describe('GET api/photos/:photoId', () => { .get('/api/photos/sign-url') .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); }); @@ -44,7 +44,7 @@ describe('GET api/photos/:photoId', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.GET_PHOTO__NOT_FOUND); + expect(res.body.status).toBe(codes.GET_PHOTO__NOT_FOUND.status); }); it('should succeed if there is a photo with the given id', async () => { @@ -54,23 +54,23 @@ describe('GET api/photos/:photoId', () => { .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.SIGN_URL__SUCCESS); + expect(res.body.status).toBe(codes.SIGN_URL__SUCCESS.status); // Perform the file upload - await utils.uploadTestPhoto(res.body.payload); + await utils.uploadTestPhoto(res.body.data); - const { key } = res.body.payload.fields; + const { key } = res.body.data.fields; res = await request(app) .get('/api/photos/confirm-upload') .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.CONFIRM_UPLOAD__SUCCESS); - expect(Number.isInteger(res.body.photoId) && res.body.photoId > 0).toBeTruthy(); + expect(res.body.status).toBe(codes.CONFIRM_UPLOAD__SUCCESS.status); + expect(Number.isInteger(res.body.data[0]) && res.body.data[0] > 0).toBeTruthy(); res = await request(app) - .get(`/api/photos/${res.body.photoId}`) + .get(`/api/photos/${res.body.data[res.body.data.length - 1]}`) .set('Authorization', me.token) .set('Accept', 'application/json') .redirects(1); diff --git a/src/server/tests/api/photos/reorder-photos.test.js b/src/server/tests/api/photos/reorder-photos.test.js index 237a9c07..63671071 100644 --- a/src/server/tests/api/photos/reorder-photos.test.js +++ b/src/server/tests/api/photos/reorder-photos.test.js @@ -36,7 +36,7 @@ describe('PATCH api/photos/reorder', () => { .patch('/api/photos/reorder') .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); }); @@ -47,7 +47,7 @@ describe('PATCH api/photos/reorder', () => { .set('Accept', 'application/json') .send({}); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data should be array'); res = await request(app) @@ -56,7 +56,7 @@ describe('PATCH api/photos/reorder', () => { .set('Accept', 'application/json') .send([]); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data should NOT have fewer than 2 items'); res = await request(app) @@ -65,7 +65,7 @@ describe('PATCH api/photos/reorder', () => { .set('Accept', 'application/json') .send([1, 2, 3, 4, 5]); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data should NOT have more than 4 items'); res = await request(app) @@ -74,7 +74,7 @@ describe('PATCH api/photos/reorder', () => { .set('Accept', 'application/json') .send([1, 2, 3, '4']); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data[3] should be number'); res = await request(app) @@ -83,7 +83,7 @@ describe('PATCH api/photos/reorder', () => { .set('Accept', 'application/json') .send([1, 2, 3, 4.4]); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data[3] should be multiple of 1'); }); @@ -94,7 +94,7 @@ describe('PATCH api/photos/reorder', () => { .set('Accept', 'application/json') .send([1, 2]); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.REORDER_PHOTOS__MISMATCHED_IDS); + expect(res.body.status).toBe(codes.REORDER_PHOTOS__MISMATCHED_IDS.status); let photoRes = await db.query(` INSERT INTO photos (user_id, index, uuid) @@ -120,7 +120,7 @@ describe('PATCH api/photos/reorder', () => { .set('Accept', 'application/json') .send([firstId, firstId + secondId]); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.REORDER_PHOTOS__MISMATCHED_IDS); + expect(res.body.status).toBe(codes.REORDER_PHOTOS__MISMATCHED_IDS.status); res = await request(app) .patch('/api/photos/reorder') @@ -128,7 +128,7 @@ describe('PATCH api/photos/reorder', () => { .set('Accept', 'application/json') .send([firstId, secondId, firstId + secondId]); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.REORDER_PHOTOS__MISMATCHED_IDS); + expect(res.body.status).toBe(codes.REORDER_PHOTOS__MISMATCHED_IDS.status); }); it('should succeed given a correct reordering', async () => { @@ -154,7 +154,7 @@ describe('PATCH api/photos/reorder', () => { .set('Accept', 'application/json') .send(newOrder); expect(reorderRes.statusCode).toBe(200); - expect(reorderRes.body.status).toBe(codes.REORDER_PHOTOS__SUCCESS); + expect(reorderRes.body.status).toEqual(codes.REORDER_PHOTOS__SUCCESS.status); photoRes = await db.query(` SELECT id @@ -164,6 +164,6 @@ describe('PATCH api/photos/reorder', () => { `, [me.id]); expect(_.isEqual(newOrder, _.map(photoRes.rows, res => res.id))).toBeTruthy(); - expect(_.isEqual(newOrder, reorderRes.body.photos)).toBeTruthy(); + expect(_.isEqual(newOrder, reorderRes.body.data)).toBeTruthy(); }); }); diff --git a/src/server/tests/api/photos/sign-url.test.js b/src/server/tests/api/photos/sign-url.test.js index eaf6ff1d..8d3f9432 100644 --- a/src/server/tests/api/photos/sign-url.test.js +++ b/src/server/tests/api/photos/sign-url.test.js @@ -33,7 +33,7 @@ describe('GET api/photos/sign-url', () => { .get('/api/photos/sign-url') .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); }); @@ -43,16 +43,15 @@ describe('GET api/photos/sign-url', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.SIGN_URL__SUCCESS); - expect(res.body.payload).toBeDefined(); - expect(res.body.payload.url).toContain('https://s3.amazonaws.com/projectgem'); - expect(res.body.payload.fields.key).toContain('photos/'); - expect(res.body.payload.fields.bucket).toContain('projectgem-'); - expect(res.body.payload.fields['X-Amz-Algorithm']).toBe('AWS4-HMAC-SHA256'); - expect(res.body.payload.fields['X-Amz-Credential']).toBeDefined(); - expect(res.body.payload.fields['X-Amz-Date']).toBeDefined(); - expect(res.body.payload.fields.Policy).toBeDefined(); - expect(res.body.payload.fields['X-Amz-Signature']).toBeDefined(); + expect(res.body.status).toBe(codes.SIGN_URL__SUCCESS.status); + expect(res.body.data.url).toContain('https://s3.amazonaws.com/projectgem'); + expect(res.body.data.fields.key).toContain('photos/'); + expect(res.body.data.fields.bucket).toContain('projectgem-'); + expect(res.body.data.fields['X-Amz-Algorithm']).toBe('AWS4-HMAC-SHA256'); + expect(res.body.data.fields['X-Amz-Credential']).toBeDefined(); + expect(res.body.data.fields['X-Amz-Date']).toBeDefined(); + expect(res.body.data.fields.Policy).toBeDefined(); + expect(res.body.data.fields['X-Amz-Signature']).toBeDefined(); }); it('should succeed given an authenticated user with a profile', async () => { @@ -66,15 +65,14 @@ describe('GET api/photos/sign-url', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.SIGN_URL__SUCCESS); - expect(res.body.payload).toBeDefined(); - expect(res.body.payload.url).toContain('https://s3.amazonaws.com/projectgem'); - expect(res.body.payload.fields.key).toContain('photos/'); - expect(res.body.payload.fields.bucket).toContain('projectgem-'); - expect(res.body.payload.fields['X-Amz-Algorithm']).toBe('AWS4-HMAC-SHA256'); - expect(res.body.payload.fields['X-Amz-Credential']).toBeDefined(); - expect(res.body.payload.fields['X-Amz-Date']).toBeDefined(); - expect(res.body.payload.fields.Policy).toBeDefined(); - expect(res.body.payload.fields['X-Amz-Signature']).toBeDefined(); + expect(res.body.status).toBe(codes.SIGN_URL__SUCCESS.status); + expect(res.body.data.url).toContain('https://s3.amazonaws.com/projectgem'); + expect(res.body.data.fields.key).toContain('photos/'); + expect(res.body.data.fields.bucket).toContain('projectgem-'); + expect(res.body.data.fields['X-Amz-Algorithm']).toBe('AWS4-HMAC-SHA256'); + expect(res.body.data.fields['X-Amz-Credential']).toBeDefined(); + expect(res.body.data.fields['X-Amz-Date']).toBeDefined(); + expect(res.body.data.fields.Policy).toBeDefined(); + expect(res.body.data.fields['X-Amz-Signature']).toBeDefined(); }); }); diff --git a/src/server/tests/api/relationships/block.test.js b/src/server/tests/api/relationships/block.test.js index cdfaed86..ce0a820c 100644 --- a/src/server/tests/api/relationships/block.test.js +++ b/src/server/tests/api/relationships/block.test.js @@ -29,7 +29,7 @@ describe('POST api/relationships/judge', () => { .get('/api/relationships/block') .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); const user = await dbUtils.createUser('jjaffe01'); @@ -38,7 +38,7 @@ describe('POST api/relationships/judge', () => { .set('Authorization', user.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(403); - expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE); + expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE.status); }); it('should fail if blockedUserId is not a number or not a multiple of 1', async () => { @@ -50,7 +50,7 @@ describe('POST api/relationships/judge', () => { blockedUserId: true, }); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data.blockedUserId should be number'); res = await request(app) @@ -61,7 +61,7 @@ describe('POST api/relationships/judge', () => { blockedUserId: 9.3, }); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data.blockedUserId should be multiple of 1'); }); @@ -74,7 +74,7 @@ describe('POST api/relationships/judge', () => { blockedUserId: -1, }); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BLOCK__USER_NOT_FOUND); + expect(res.body.status).toBe(codes.BLOCK__USER_NOT_FOUND.status); }); it('should allow a user without a profile setup to be blocked', async () => { @@ -88,7 +88,7 @@ describe('POST api/relationships/judge', () => { blockedUserId: user.id, }); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.BLOCK__SUCCESS); + expect(res.body.status).toBe(codes.BLOCK__SUCCESS.status); }); it('should allow a user with a profile to be blocked', async () => { @@ -101,6 +101,6 @@ describe('POST api/relationships/judge', () => { blockedUserId: user.id, }); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.BLOCK__SUCCESS); + expect(res.body.status).toBe(codes.BLOCK__SUCCESS.status); }); }); diff --git a/src/server/tests/api/relationships/get-matches.test.js b/src/server/tests/api/relationships/get-matches.test.js index ebb33ed0..dcfd7b2b 100644 --- a/src/server/tests/api/relationships/get-matches.test.js +++ b/src/server/tests/api/relationships/get-matches.test.js @@ -29,7 +29,7 @@ describe('GET api/relationships/matches', () => { .get('/api/relationships/matches') .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); const user = await dbUtils.createUser('jjaffe01'); @@ -38,7 +38,7 @@ describe('GET api/relationships/matches', () => { .set('Authorization', user.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(403); - expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE); + expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE.status); }); it('should not return any matches if the user has no relationships', async () => { @@ -48,7 +48,7 @@ describe('GET api/relationships/matches', () => { .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.GET_MATCHES__SUCCESS); + expect(res.body.status).toBe(codes.GET_MATCHES__SUCCESS.status); }); it('should return a match given a relationship with inverse likes on smash', async () => { @@ -61,9 +61,9 @@ describe('GET api/relationships/matches', () => { .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.GET_MATCHES__SUCCESS); - expect(res.body.matches.length).toBe(1); - const match = res.body.matches[0]; + expect(res.body.status).toBe(codes.GET_MATCHES__SUCCESS.status); + expect(res.body.data.length).toBe(1); + const match = res.body.data[0]; expect(match.userId).toBe(other.id); expect(match.scenes).toEqual(['smash']); }); @@ -78,11 +78,11 @@ describe('GET api/relationships/matches', () => { .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.GET_MATCHES__SUCCESS); - expect(res.body.matches.length).toBe(2); - const personMatch = (res.body.matches[0].id === person.id) - ? res.body.matches[0] - : res.body.matches[1]; + expect(res.body.status).toBe(codes.GET_MATCHES__SUCCESS.status); + expect(res.body.data.length).toBe(2); + const personMatch = (res.body.data[0].id === person.id) + ? res.body.data[0] + : res.body.data[1]; expect(personMatch.userId).toBe(person.id); expect(personMatch.scenes.sort()).toEqual(['smash', 'stone', 'social'].sort()); }); @@ -97,8 +97,9 @@ describe('GET api/relationships/matches', () => { .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.GET_MATCHES__SUCCESS); - expect(res.body.matches.length).toBe(2); // We should not recieve a result for the blocked one + expect(res.body.status).toBe(codes.GET_MATCHES__SUCCESS.status); + // We should not recieve a result for the blocked one + expect(res.body.data.length).toBe(2); }); // Check that a relationship with likes but also blocks does not get matched diff --git a/src/server/tests/api/relationships/get-scene-candidates.test.js b/src/server/tests/api/relationships/get-scene-candidates.test.js index 43ed80b9..35e36c93 100644 --- a/src/server/tests/api/relationships/get-scene-candidates.test.js +++ b/src/server/tests/api/relationships/get-scene-candidates.test.js @@ -93,7 +93,7 @@ describe('GET api/relationships/candidates/:scene', () => { .get('/api/relationships/candidates/smash') .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); const user = await dbUtils.createUser('mgreen01'); @@ -102,7 +102,7 @@ describe('GET api/relationships/candidates/:scene', () => { .set('Authorization', user.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(403); - expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE); + expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE.status); }); it('should only allow valid scenes (smash, social, stone)', async () => { @@ -117,42 +117,42 @@ describe('GET api/relationships/candidates/:scene', () => { .set('Authorization', user.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.GET_SCENE_CANDIDATES__SUCCESS); + expect(res.body.status).toBe(codes.GET_SCENE_CANDIDATES__SUCCESS.status); res = await request(app) .get('/api/relationships/candidates/social') .set('Authorization', user.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.GET_SCENE_CANDIDATES__SUCCESS); + expect(res.body.status).toBe(codes.GET_SCENE_CANDIDATES__SUCCESS.status); res = await request(app) .get('/api/relationships/candidates/stone') .set('Authorization', user.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.GET_SCENE_CANDIDATES__SUCCESS); + expect(res.body.status).toBe(codes.GET_SCENE_CANDIDATES__SUCCESS.status); res = await request(app) .get('/api/relationships/candidates/smsh') .set('Authorization', user.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.GET_SCENE_CANDIDATES__INVALID_SCENE); + expect(res.body.status).toBe(codes.GET_SCENE_CANDIDATES__INVALID_SCENE.status); res = await request(app) .get('/api/relationships/candidates/aoei-aoeuu-aoe') .set('Authorization', user.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.GET_SCENE_CANDIDATES__INVALID_SCENE); + expect(res.body.status).toBe(codes.GET_SCENE_CANDIDATES__INVALID_SCENE.status); res = await request(app) .get('/api/relationships/candidates/stinky') .set('Authorization', user.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.GET_SCENE_CANDIDATES__INVALID_SCENE); + expect(res.body.status).toBe(codes.GET_SCENE_CANDIDATES__INVALID_SCENE.status); res = await request(app) .get('/api/relationships/candidates') @@ -167,7 +167,7 @@ describe('GET api/relationships/candidates/:scene', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(Array.isArray(res.body.candidates)).toBeTruthy(); + expect(Array.isArray(res.body.data)).toBeTruthy(); }); it('should only return <10 candidates that are active in the scene', async () => { @@ -177,10 +177,10 @@ describe('GET api/relationships/candidates/:scene', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(_.every(res.body.candidates, (candidate) => { + expect(_.every(res.body.data, (candidate) => { return users[candidate.userId].settings.activeSmash; })).toBeTruthy(); - expect(res.body.candidates.length).toBeLessThanOrEqual(10); + expect(res.body.data.length).toBeLessThanOrEqual(10); // Stone res = await request(app) @@ -188,10 +188,10 @@ describe('GET api/relationships/candidates/:scene', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(_.every(res.body.candidates, (candidate) => { + expect(_.every(res.body.data, (candidate) => { return users[candidate.userId.toString()].settings.activeSocial; })).toBeTruthy(); - expect(res.body.candidates.length).toBeLessThanOrEqual(10); + expect(res.body.data.length).toBeLessThanOrEqual(10); // Social res = await request(app) @@ -199,10 +199,10 @@ describe('GET api/relationships/candidates/:scene', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(_.every(res.body.candidates, (candidate) => { + expect(_.every(res.body.data, (candidate) => { return users[candidate.userId.toString()].settings.activeStone; })).toBeTruthy(); - expect(res.body.candidates.length).toBeLessThanOrEqual(10); + expect(res.body.data.length).toBeLessThanOrEqual(10); }); // Check that only profiles that have not yet been liked show up @@ -220,10 +220,10 @@ describe('GET api/relationships/candidates/:scene', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(_.every(res.body.candidates, (candidate) => { + expect(_.every(res.body.data, (candidate) => { return users[candidate.userId].settings.activeSmash; })).toBeTruthy(); - expect(res.body.candidates.length).toBeLessThanOrEqual(5); + expect(res.body.data.length).toBeLessThanOrEqual(5); }); it('should only show the "correct" profiles for a given scene', async () => { @@ -233,12 +233,12 @@ describe('GET api/relationships/candidates/:scene', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(_.every(res.body.candidates, (candidate) => { + expect(_.every(res.body.data, (candidate) => { return _.find(nonLiked, (person) => { return person.id === candidate.userId; }); })).toBeTruthy(); - expect(res.body.candidates.length).toBeLessThanOrEqual(5); + expect(res.body.data.length).toBeLessThanOrEqual(5); }); it('should not return a blocked user', async () => { @@ -256,11 +256,11 @@ describe('GET api/relationships/candidates/:scene', () => { .set('Authorization', me.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(200); - expect(_.every(res.body.candidates, (candidate) => { + expect(_.every(res.body.data, (candidate) => { return _.find(nonLikedNotBlocked, (person) => { return person.id === candidate.userId; }); })).toBeTruthy(); - expect(res.body.candidates.length).toBeLessThanOrEqual(4); + expect(res.body.data.length).toBeLessThanOrEqual(4); }); }); diff --git a/src/server/tests/api/relationships/judge.test.js b/src/server/tests/api/relationships/judge.test.js index 6e5c7284..5a673893 100644 --- a/src/server/tests/api/relationships/judge.test.js +++ b/src/server/tests/api/relationships/judge.test.js @@ -29,7 +29,7 @@ describe('POST api/relationships/judge', () => { .get('/api/relationships/judge') .set('Accept', 'application/json'); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); const user = await dbUtils.createUser('jjaffe01'); @@ -38,7 +38,7 @@ describe('POST api/relationships/judge', () => { .set('Authorization', user.token) .set('Accept', 'application/json'); expect(res.statusCode).toBe(403); - expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE); + expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE.status); }); it('should fail if candidateUserId is not a number or not a multiple of 1', async () => { @@ -52,7 +52,7 @@ describe('POST api/relationships/judge', () => { liked: true, }); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data.candidateUserId should be number'); res = await request(app) @@ -65,7 +65,7 @@ describe('POST api/relationships/judge', () => { liked: true, }); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data.candidateUserId should be multiple of 1'); }); @@ -80,7 +80,7 @@ describe('POST api/relationships/judge', () => { liked: 'true', }); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data.liked should be boolean'); }); @@ -95,7 +95,7 @@ describe('POST api/relationships/judge', () => { liked: true, }); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data.scene should be string'); res = await request(app) @@ -108,7 +108,7 @@ describe('POST api/relationships/judge', () => { liked: true, }); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data.scene should be equal to one of the allowed values'); res = await request(app) @@ -121,7 +121,7 @@ describe('POST api/relationships/judge', () => { liked: true, }); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('data.scene should be equal to one of the allowed values'); }); @@ -136,7 +136,7 @@ describe('POST api/relationships/judge', () => { liked: true, }); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.JUDGE__CANDIDATE_NOT_FOUND); + expect(res.body.status).toBe(codes.JUDGE__CANDIDATE_NOT_FOUND.status); }); it('should allow a user without a profile setup to be judged', async () => { @@ -152,7 +152,7 @@ describe('POST api/relationships/judge', () => { liked: true, }); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.JUDGE__SUCCESS); + expect(res.body.status).toBe(codes.JUDGE__SUCCESS.status); }); it('should allow a candidate with a profile to be liked on any scene', async () => { @@ -167,7 +167,7 @@ describe('POST api/relationships/judge', () => { liked: true, }); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.JUDGE__SUCCESS); + expect(res.body.status).toBe(codes.JUDGE__SUCCESS.status); res = await request(app) .post('/api/relationships/judge') @@ -179,7 +179,7 @@ describe('POST api/relationships/judge', () => { liked: false, }); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.JUDGE__SUCCESS); + expect(res.body.status).toBe(codes.JUDGE__SUCCESS.status); res = await request(app) .post('/api/relationships/judge') @@ -191,7 +191,7 @@ describe('POST api/relationships/judge', () => { liked: true, }); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.JUDGE__SUCCESS); + expect(res.body.status).toBe(codes.JUDGE__SUCCESS.status); }); it('should allow a user to be disliked after being liked on a scene', async () => { @@ -206,7 +206,7 @@ describe('POST api/relationships/judge', () => { liked: true, }); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.JUDGE__SUCCESS); + expect(res.body.status).toBe(codes.JUDGE__SUCCESS.status); res = await request(app) .post('/api/relationships/judge') @@ -218,6 +218,6 @@ describe('POST api/relationships/judge', () => { liked: false, }); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.JUDGE__SUCCESS); + expect(res.body.status).toBe(codes.JUDGE__SUCCESS.status); }); }); diff --git a/src/server/tests/api/users/create-my-profile.test.js b/src/server/tests/api/users/finalize-profile-setup.test.js similarity index 81% rename from src/server/tests/api/users/create-my-profile.test.js rename to src/server/tests/api/users/finalize-profile-setup.test.js index a0bd7fd6..6355a4a0 100644 --- a/src/server/tests/api/users/create-my-profile.test.js +++ b/src/server/tests/api/users/finalize-profile-setup.test.js @@ -24,7 +24,7 @@ describe('POST api/users/me/profile', () => { .set('Authorization', 'this-is-not-a-valid-json-web-token') .send({}) .expect(401); - expect(res.body.status).toBe(codes.UNAUTHORIZED); + expect(res.body.status).toBe(codes.UNAUTHORIZED.status); }); it('should error if the user does not exist for the given auth token', async () => { @@ -34,7 +34,7 @@ describe('POST api/users/me/profile', () => { .set('Authorization', await dbUtils.signToken(1)) .send({}) .expect(401); - expect(res.body.status).toBe(codes.UNAUTHORIZED); + expect(res.body.status).toBe(codes.UNAUTHORIZED.status); }); it('should not allow a user without a photo to create a profile', async () => { @@ -49,12 +49,12 @@ describe('POST api/users/me/profile', () => { birthday: '1997-09-09', }); expect(res.status).toBe(409); - expect(res.body.status).toBe(codes.CREATE_PROFILE__PHOTO_REQUIRED); + expect(res.body.status).toBe(codes.FINALIZE_PROFILE_SETUP__PHOTO_REQUIRED.status); }); it('should succeed if the user has been created and has uploaded a photo yet does not yet have a profile', async () => { const user = await dbUtils.createUser('mgreen13'); - await dbUtils.insertPhoto(user.id); + const photoId = await dbUtils.insertPhoto(user.id); const res = await request(app) .post('/api/users/me/profile') .set('Accept', 'application/json') @@ -65,7 +65,12 @@ describe('POST api/users/me/profile', () => { birthday: '1997-09-09', }) .expect(201); - expect(res.body.status).toBe(codes.CREATE_PROFILE__SUCCESS); + expect(res.body.status).toBe(codes.FINALIZE_PROFILE_SETUP__SUCCESS.status); + expect(res.body.data).toBeDefined(); + expect(res.body.data.photoIds).toEqual([photoId]); + expect(res.body.data.fields.displayName).toBe('Max'); + expect(res.body.data.fields.bio).toBe('He is a guy'); + expect(res.body.data.fields.birthday).toBe('1997-09-09'); }); it('should fail if the user has already created a profile', async () => { @@ -85,7 +90,7 @@ describe('POST api/users/me/profile', () => { birthday: '1997-09-09', }) .expect(409); - expect(res.body.status).toBe(codes.CREATE_PROFILE__PROFILE_ALREADY_CREATED); + expect(res.body.status).toBe(codes.FINALIZE_PROFILE_SETUP__PROFILE_ALREADY_CREATED.status); }); it('should return bad request if displayName is not included', async () => { @@ -99,7 +104,7 @@ describe('POST api/users/me/profile', () => { birthday: '1997-09-09', }) .expect(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toContain('displayName'); }); @@ -114,7 +119,7 @@ describe('POST api/users/me/profile', () => { bio: 'He is a guy', }) .expect(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toContain('birthday'); }); @@ -129,7 +134,7 @@ describe('POST api/users/me/profile', () => { birthday: '1997-09-09', }) .expect(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toContain('bio'); }); @@ -145,8 +150,8 @@ describe('POST api/users/me/profile', () => { birthday: '1997-09-09', }) .expect(400); - expect(res.body.status).toBe(codes.CREATE_PROFILE__INVALID_REQUEST); - expect(res.body.message).toBe(profileErrorMessages.DISPLAY_NAME_TOO_LONG); + expect(res.body.status).toBe(codes.FINALIZE_PROFILE_SETUP__INVALID_REQUEST.status); + expect(res.body.data.message).toBe(profileErrorMessages.DISPLAY_NAME_TOO_LONG); }); it('should error if the bio is too long (>500 characters)', async () => { @@ -166,8 +171,8 @@ describe('POST api/users/me/profile', () => { birthday: '1997-09-09', }) .expect(400); - expect(res.body.status).toBe(codes.CREATE_PROFILE__INVALID_REQUEST); - expect(res.body.message).toBe(profileErrorMessages.BIO_TOO_LONG); + expect(res.body.status).toBe(codes.FINALIZE_PROFILE_SETUP__INVALID_REQUEST.status); + expect(res.body.data.message).toBe(profileErrorMessages.BIO_TOO_LONG); }); it('should ensure the date is formatted correctly', async () => { @@ -182,7 +187,7 @@ describe('POST api/users/me/profile', () => { birthday: '1997-099-09', }) .expect(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toContain('should match format "date"'); }); @@ -198,8 +203,8 @@ describe('POST api/users/me/profile', () => { birthday: '1980-10-09', }) .expect(400); - expect(res.body.status).toBe(codes.CREATE_PROFILE__INVALID_REQUEST); - expect(res.body.message).toBe(profileErrorMessages.BIRTHDAY_NOT_VALID); + expect(res.body.status).toBe(codes.FINALIZE_PROFILE_SETUP__INVALID_REQUEST.status); + expect(res.body.data.message).toBe(profileErrorMessages.BIRTHDAY_NOT_VALID); }); it('should allow for all fields to be present and ensure they get stored in the db', async () => { @@ -216,7 +221,7 @@ describe('POST api/users/me/profile', () => { birthday, }) .expect(201); - expect(res.body.status).toBe(codes.CREATE_PROFILE__SUCCESS); + expect(res.body.status).toBe(codes.FINALIZE_PROFILE_SETUP__SUCCESS.status); const profileResult = await db.query(` SELECT *, to_char("birthday", 'YYYY-MM-DD') AS birthday_date diff --git a/src/server/tests/api/users/get-my-photos.test.js b/src/server/tests/api/users/get-my-photos.test.js index 594566ed..30b451c0 100644 --- a/src/server/tests/api/users/get-my-photos.test.js +++ b/src/server/tests/api/users/get-my-photos.test.js @@ -28,7 +28,7 @@ describe('GET api/users/me/photos', () => { .get('/api/users/me/photos') .set('Accept', 'application/json') .expect(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); }); @@ -56,7 +56,7 @@ describe('GET api/users/me/photos', () => { .set('Accept', 'application/json') .set('Authorization', me.token); expect(res.statusCode).toBe(200); - expect(res.body.status).toBe(codes.GET_MY_PHOTOS__SUCCESS); - expect(res.body.photoIds).toEqual([firstId, secondId]); + expect(res.body.status).toBe(codes.GET_MY_PHOTOS__SUCCESS.status); + expect(res.body.data).toEqual([firstId, secondId]); }); }); diff --git a/src/server/tests/api/users/get-my-profile.test.js b/src/server/tests/api/users/get-my-profile.test.js index e05c977b..c6874e1e 100644 --- a/src/server/tests/api/users/get-my-profile.test.js +++ b/src/server/tests/api/users/get-my-profile.test.js @@ -21,7 +21,7 @@ describe('GET api/users/me/profile', () => { .get('/api/users/me/profile') .set('Accept', 'application/json') .expect(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); const user = await dbUtils.createUser('mgreen14'); @@ -30,7 +30,7 @@ describe('GET api/users/me/profile', () => { .set('Authorization', user.token) .set('Accept', 'application/json') .expect(403); - expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE); + expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE.status); }); it('should return the profile of the current user', async () => { @@ -47,11 +47,11 @@ describe('GET api/users/me/profile', () => { .set('Authorization', user.token) .set('Accept', 'application/json') .expect(200); - expect(res.body.status).toBe(codes.GET_PROFILE__SUCCESS); - expect(res.body.profile).toBeDefined(); + expect(res.body.status).toBe(codes.GET_PROFILE__SUCCESS.status); + expect(res.body.data).toBeDefined(); - expect(res.body.profile.displayName).toBe(profile.displayName); - expect(res.body.profile.birthday).toBe(profile.birthday); - expect(res.body.profile.bio).toBe(profile.bio); + expect(res.body.data.fields.displayName).toBe(profile.displayName); + expect(res.body.data.fields.birthday).toBe(profile.birthday); + expect(res.body.data.fields.bio).toBe(profile.bio); }); }); diff --git a/src/server/tests/api/users/get-my-settings.test.js b/src/server/tests/api/users/get-my-settings.test.js index c80ed75b..c90bf407 100644 --- a/src/server/tests/api/users/get-my-settings.test.js +++ b/src/server/tests/api/users/get-my-settings.test.js @@ -21,7 +21,7 @@ describe('GET api/users/me/settings', () => { .get('/api/users/me/settings') .set('Accept', 'application/json') .expect(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); const user = await dbUtils.createUser('mgreen14'); @@ -30,7 +30,7 @@ describe('GET api/users/me/settings', () => { .set('Authorization', user.token) .set('Accept', 'application/json') .expect(403); - expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE); + expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE.status); }); it('should return the settings of the current user', async () => { @@ -47,17 +47,17 @@ describe('GET api/users/me/settings', () => { .set('Authorization', user.token) .set('Accept', 'application/json') .expect(200); - expect(res.body.status).toBe(codes.GET_SETTINGS__SUCCESS); - expect(res.body.settings).toBeDefined(); - - expect(res.body.settings.usePronouns).toBeDefined(); - expect(res.body.settings.wantPronouns).toBeDefined(); - - expect(res.body.settings.usePronouns.he).toBeDefined(); - expect(res.body.settings.usePronouns.she).toBeDefined(); - expect(res.body.settings.usePronouns.they).toBeDefined(); - expect(res.body.settings.wantPronouns.he).toBeDefined(); - expect(res.body.settings.wantPronouns.she).toBeDefined(); - expect(res.body.settings.wantPronouns.they).toBeDefined(); + expect(res.body.status).toBe(codes.GET_SETTINGS__SUCCESS.status); + expect(res.body.data).toBeDefined(); + + expect(res.body.data.usePronouns).toBeDefined(); + expect(res.body.data.wantPronouns).toBeDefined(); + + expect(res.body.data.usePronouns.he).toBeDefined(); + expect(res.body.data.usePronouns.she).toBeDefined(); + expect(res.body.data.usePronouns.they).toBeDefined(); + expect(res.body.data.wantPronouns.he).toBeDefined(); + expect(res.body.data.wantPronouns.she).toBeDefined(); + expect(res.body.data.wantPronouns.they).toBeDefined(); }); }); diff --git a/src/server/tests/api/users/get-profile.test.js b/src/server/tests/api/users/get-profile.test.js index 85128022..1d05fcb9 100644 --- a/src/server/tests/api/users/get-profile.test.js +++ b/src/server/tests/api/users/get-profile.test.js @@ -44,7 +44,7 @@ describe('GET api/users/:userId/profile', () => { let res = await request(app) .get('/api/users/1/profile') .set('Accept', 'application/json'); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toBe('Missing Authorization header.'); const tempUser = await dbUtils.createUser('mgreen14'); @@ -53,7 +53,7 @@ describe('GET api/users/:userId/profile', () => { .set('Authorization', tempUser.token) .set('Accept', 'application/json') .expect(403); - expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE); + expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE.status); }); it('should be able to get the current user profile', async () => { @@ -62,14 +62,14 @@ describe('GET api/users/:userId/profile', () => { .set('Authorization', user.token) .set('Accept', 'application/json') .expect(200); - expect(res.body.status).toBe(codes.GET_PROFILE__SUCCESS); - expect(res.body.profile).toBeDefined(); - - expect(res.body.profile.displayName).toBe(user.profile.displayName); - expect(res.body.profile.birthday).toBe(user.profile.birthday); - expect(res.body.profile.bio).toBe(user.profile.bio); - expect(res.body.profile.photos).toBeDefined(); - expect(res.body.profile.photos[0] > 0).toBeTruthy(); + expect(res.body.status).toBe(codes.GET_PROFILE__SUCCESS.status); + expect(res.body.data).toBeDefined(); + + expect(res.body.data.fields.displayName).toBe(user.profile.displayName); + expect(res.body.data.fields.birthday).toBe(user.profile.birthday); + expect(res.body.data.fields.bio).toBe(user.profile.bio); + expect(res.body.data.photoIds).toBeDefined(); + expect(res.body.data.photoIds[0] > 0).toBeTruthy(); }); it('should be able to get the another user profile', async () => { @@ -78,12 +78,12 @@ describe('GET api/users/:userId/profile', () => { .set('Authorization', user.token) .set('Accept', 'application/json') .expect(200); - expect(res.body.status).toBe(codes.GET_PROFILE__SUCCESS); - expect(res.body.profile).toBeDefined(); + expect(res.body.status).toBe(codes.GET_PROFILE__SUCCESS.status); + expect(res.body.data).toBeDefined(); - expect(res.body.profile.displayName).toBe(otherUser.profile.displayName); - expect(res.body.profile.birthday).toBe(otherUser.profile.birthday); - expect(res.body.profile.bio).toBe(otherUser.profile.bio); + expect(res.body.data.fields.displayName).toBe(otherUser.profile.displayName); + expect(res.body.data.fields.birthday).toBe(otherUser.profile.birthday); + expect(res.body.data.fields.bio).toBe(otherUser.profile.bio); }); it('should fail to get another user profile if that user has not setup a profile', async () => { @@ -92,7 +92,7 @@ describe('GET api/users/:userId/profile', () => { .set('Authorization', user.token) .set('Accept', 'application/json') .expect(404); - expect(res.body.status).toBe(codes.GET_PROFILE__PROFILE_NOT_FOUND); + expect(res.body.status).toBe(codes.GET_PROFILE__PROFILE_NOT_FOUND.status); }); it('should error if the user id is not an integer', async () => { @@ -109,6 +109,6 @@ describe('GET api/users/:userId/profile', () => { .set('Authorization', user.token) .set('Accept', 'application/json') .expect(404); - expect(res.body.status).toBe(codes.GET_PROFILE__PROFILE_NOT_FOUND); + expect(res.body.status).toBe(codes.GET_PROFILE__PROFILE_NOT_FOUND.status); }); }); diff --git a/src/server/tests/api/users/update-my-profile.test.js b/src/server/tests/api/users/update-my-profile.test.js index 91c0c345..7350ad2f 100644 --- a/src/server/tests/api/users/update-my-profile.test.js +++ b/src/server/tests/api/users/update-my-profile.test.js @@ -24,7 +24,7 @@ describe('PATCH api/users/me/profile', () => { .set('Authorization', 'this-is-not-a-valid-json-web-token') .send({}) .expect(401); - expect(res.body.status).toBe(codes.UNAUTHORIZED); + expect(res.body.status).toBe(codes.UNAUTHORIZED.status); }); it('should error if the user does not exist for the given auth token', async () => { @@ -34,7 +34,7 @@ describe('PATCH api/users/me/profile', () => { .set('Authorization', await dbUtils.signToken(1)) .send({}) .expect(401); - expect(res.body.status).toBe(codes.UNAUTHORIZED); + expect(res.body.status).toBe(codes.UNAUTHORIZED.status); }); it('should fail if the user has been created but does not yet have a profile', async () => { @@ -49,7 +49,7 @@ describe('PATCH api/users/me/profile', () => { birthday: '1997-09-09', }) .expect(403); - expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE); + expect(res.body.status).toBe(codes.PROFILE_SETUP_INCOMPLETE.status); }); it('should succeed if the user has already created a profile', async () => { @@ -68,7 +68,7 @@ describe('PATCH api/users/me/profile', () => { birthday: '1999-09-09', }) .expect(201); - expect(res.body.status).toBe(codes.UPDATE_PROFILE__SUCCESS); + expect(res.body.status).toBe(codes.UPDATE_PROFILE__SUCCESS.status); }); it('should succeed if no fields are included', async () => { @@ -84,7 +84,7 @@ describe('PATCH api/users/me/profile', () => { .set('Authorization', user.token) .send({}) .expect(201); - expect(res.body.status).toBe(codes.UPDATE_PROFILE__SUCCESS); + expect(res.body.status).toBe(codes.UPDATE_PROFILE__SUCCESS.status); }); it('should error if the display name is too long (>50 characters)', async () => { @@ -104,8 +104,8 @@ describe('PATCH api/users/me/profile', () => { birthday: '1997-09-09', }) .expect(400); - expect(res.body.status).toBe(codes.UPDATE_PROFILE__INVALID_REQUEST); - expect(res.body.message).toBe(profileErrorMessages.DISPLAY_NAME_TOO_LONG); + expect(res.body.status).toBe(codes.UPDATE_PROFILE__INVALID_REQUEST.status); + expect(res.body.data.message).toBe(profileErrorMessages.DISPLAY_NAME_TOO_LONG); }); it('should error if the bio is too long (>500 characters)', async () => { @@ -130,8 +130,8 @@ describe('PATCH api/users/me/profile', () => { birthday: '1997-09-09', }) .expect(400); - expect(res.body.status).toBe(codes.UPDATE_PROFILE__INVALID_REQUEST); - expect(res.body.message).toBe(profileErrorMessages.BIO_TOO_LONG); + expect(res.body.status).toBe(codes.UPDATE_PROFILE__INVALID_REQUEST.status); + expect(res.body.data.message).toBe(profileErrorMessages.BIO_TOO_LONG); }); it('should ensure the date is formatted correctly', async () => { @@ -151,7 +151,7 @@ describe('PATCH api/users/me/profile', () => { birthday: '1997-099-09', }) .expect(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); expect(res.body.message).toContain('should match format "date"'); }); @@ -172,8 +172,8 @@ describe('PATCH api/users/me/profile', () => { birthday: '1980-10-09', }) .expect(400); - expect(res.body.status).toBe(codes.UPDATE_PROFILE__INVALID_REQUEST); - expect(res.body.message).toBe(profileErrorMessages.BIRTHDAY_NOT_VALID); + expect(res.body.status).toBe(codes.UPDATE_PROFILE__INVALID_REQUEST.status); + expect(res.body.data.message).toBe(profileErrorMessages.BIRTHDAY_NOT_VALID); }); it('should allow for all fields to be present and ensure they get stored in the db', async () => { @@ -194,7 +194,7 @@ describe('PATCH api/users/me/profile', () => { birthday, }) .expect(201); - expect(res.body.status).toBe(codes.UPDATE_PROFILE__SUCCESS); + expect(res.body.status).toBe(codes.UPDATE_PROFILE__SUCCESS.status); const profileResult = await db.query(` SELECT *, to_char("birthday", 'YYYY-MM-DD') AS birthday_date diff --git a/src/server/tests/api/users/update-my-settings.test.js b/src/server/tests/api/users/update-my-settings.test.js index 862feb94..2e78eedb 100644 --- a/src/server/tests/api/users/update-my-settings.test.js +++ b/src/server/tests/api/users/update-my-settings.test.js @@ -23,7 +23,7 @@ describe('GET api/users/me/settings', () => { .set('Authorization', 'this-is-not-a-valid-json-web-token') .send({}) .expect(401); - expect(res.body.status).toBe(codes.UNAUTHORIZED); + expect(res.body.status).toBe(codes.UNAUTHORIZED.status); }); it('should error if the user does not exist for the given auth token', async () => { @@ -33,7 +33,7 @@ describe('GET api/users/me/settings', () => { .set('Authorization', await dbUtils.signToken(1)) .send({}) .expect(401); - expect(res.body.status).toBe(codes.UNAUTHORIZED); + expect(res.body.status).toBe(codes.UNAUTHORIZED.status); }); it('should succeed if there is a empty body', async () => { @@ -49,7 +49,7 @@ describe('GET api/users/me/settings', () => { .set('Authorization', user.token) .send({}); expect(res.statusCode).toBe(201); - expect(res.body.status).toBe(codes.UPDATE_SETTINGS__SUCCESS); + expect(res.body.status).toBe(codes.UPDATE_SETTINGS__SUCCESS.status); }); it('should succeed in updating all of the pronoun preferences', async () => { @@ -76,7 +76,7 @@ describe('GET api/users/me/settings', () => { }, }); expect(res.statusCode).toBe(201); - expect(res.body.status).toBe(codes.UPDATE_SETTINGS__SUCCESS); + expect(res.body.status).toBe(codes.UPDATE_SETTINGS__SUCCESS.status); }); it('should fail if the the pronoun preferences are not booleans', async () => { @@ -103,7 +103,7 @@ describe('GET api/users/me/settings', () => { }, }); expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.body.status).toBe(codes.BAD_REQUEST.status); }); it('should succeed in only updating some of the user settings at once', async () => { @@ -120,13 +120,23 @@ describe('GET api/users/me/settings', () => { .send({ wantPronouns: { he: true, - they: 'true', + they: true, }, usePronouns: { he: true, }, }); - expect(res.statusCode).toBe(400); - expect(res.body.status).toBe(codes.BAD_REQUEST); + expect(res.statusCode).toBe(201); + expect(res.body.status).toBe(codes.UPDATE_SETTINGS__SUCCESS.status); + + expect(res.body.data.usePronouns).toBeDefined(); + expect(res.body.data.wantPronouns).toBeDefined(); + + expect(res.body.data.usePronouns.he).toBeDefined(); + expect(res.body.data.usePronouns.she).toBeDefined(); + expect(res.body.data.usePronouns.they).toBeDefined(); + expect(res.body.data.wantPronouns.he).toBeDefined(); + expect(res.body.data.wantPronouns.she).toBeDefined(); + expect(res.body.data.wantPronouns.they).toBeDefined(); }); }); diff --git a/src/server/tests/utils/db.js b/src/server/tests/utils/db.js index 4982446b..989c725d 100644 --- a/src/server/tests/utils/db.js +++ b/src/server/tests/utils/db.js @@ -45,16 +45,16 @@ async function createProfile(userId, body) { throw new Error('Invalid profile supplied to createUser'); } - const photoId = await insertPhoto(userId, 1); + await insertPhoto(userId, 1); try { const result = await db.query(` INSERT INTO profiles - (user_id, display_name, birthday, bio, splash_photo_id) - VALUES ($1, $2, $3, $4, $5) + (user_id, display_name, birthday, bio) + VALUES ($1, $2, $3, $4) RETURNING user_id AS "userId" `, - [userId, displayName, birthday, bio, photoId]); + [userId, displayName, birthday, bio]); return result.rows[0].userId; } catch (error) { throw new Error('Failed to insert profile');