Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1. BIG server clean & API response schema standardization #266

Merged
merged 17 commits into from
Feb 8, 2019

Conversation

mgreenw
Copy link
Owner

@mgreenw mgreenw commented Feb 3, 2019

Ok so I did this in a really dumb order and never committed and it was bad. I'm really sorry. I will not do that again. Here's what changed:

  • Removed "splash photo" and removed restriction on deleting the last photo
  • Standardized API response: every endpoint that returns data now returns it in a "data" field.
  • Centralized status code location to match response body status.
  • Standardized profile schema:
{
  fields: {
    displayName: string,
    bio: string,
    birthday: string
  },
  photoIds: Array<number>
}

@mgreenw mgreenw requested a review from JacobJaffe February 3, 2019 23:27
FROM inserted
)
) AS "photoIds"
`, [userId, photoCount + 1, uuid, userId]);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return the new photoIds!

return apiUtils.status(409).json({
status: codes.DELETE_PHOTO__CANNOT_DELETE_LAST_PHOTO,
});
return apiUtils.status(codes.DELETE_PHOTO__NOT_FOUND).noData();
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removes the server check that it is a user's last photo.

SET splash_photo_id = $1
WHERE user_id = $2
`, [photos[0].id, userId]);
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete "splash photo id"

${updatedPhotos.join(',')}
) AS updated_photos (id, index)
WHERE photos.id = updated_photos.id
`);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to reorder the photos if there are no photos! This just wraps the old query in an if statement


return apiUtils.status(codes.DELETE_PHOTO__SUCCESS).data(
_.map(photos, photo => photo.id),
);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return the new photo order

status: codes.GET_PHOTO__NOT_FOUND,
});
// $FlowFixMe I'm really not sure what's going on here...it's only HERE
return apiUtils.status(codes.GET_PHOTO__NOT_FOUND).noData();
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is weird...I'm not worried about it and I'm pretty sure it's flow getting confused. Will look into it more

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spent a little time debugging this. I think the root of this is the implicit types returned by functions; feels like it should work, perhaps related to:
facebook/flow#5294

I tested out a solution there by explicitly typing the return type (which is safe, as flow does make sure it's a valid return type).

    const foo: { body: { status: string }, statusCode: number } = apiUtils
      .status(codes.GET_PHOTO__NOT_FOUND)
      .noData();
    return foo;

Worth looking into later the deeper cause of this, but how about for now at least keeping this with a TODO comment so that the export here is well typed

// 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);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handles the redirect. Internally, we still have a GET_PHOTO__SUCCCES response

return apiUtils.status(200).json({
status: codes.REORDER_PHOTOS__SUCCESS,
photos: newOrder,
});
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete stuff about splash photo

@@ -4,6 +4,7 @@ import type { $Request } from 'express';

const db = require('../../db');
const apiUtils = require('../utils');
const { profileSelectQuery } = require('../users/utils');
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows for a standard profile select query. This is nice and standardized and I use it in a few places.

candidates: result.rows,
});

`, [userId, excludedUsers]);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized you can reuse paramaters...duh. Also it is better practice to USE paramaters

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
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returns it in the format we discussed with profile, fields, and photoIds

last_swipe_timestamp = now()
`, [userId, candidateUserId, liked, liked]);
`, [userId, candidateUserId, liked]);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can reuse params

@@ -1,173 +1,221 @@
// @flow
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole file is changed and just has nicer abstraction over the 'code'. Much cleaner now

@@ -3,7 +3,7 @@
import type { $Request } from 'express';

const apiUtils = require('../utils');
const utils = require('./utils');
const { validateProfile, profileSelectQuery } = require('./utils');
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to do imports like this

SET display_name = EXCLUDED.display_name
RETURNING
${profileSelectQuery('$5')},
(xmax::text::int > 0) as existed
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is cool. 'existed' checks to see if it hit a conflict or not. If existed is true, then there was a conflict.

Copy link
Collaborator

@JacobJaffe JacobJaffe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some comments during my 15 break in class haha

return apiUtils.status(400).json({
status: codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_FOUND,
});
return apiUtils.status(codes.SEND_VERIFICATION_EMAIL__UTLN_NOT_FOUND).noData();
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: lots of reuse of .classyear and such, in general on these I think it would be a good idea to use destructure {classyear} = memberinfo in these

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally agree...I think that's a separate PR. I started doing that for some util functions too and I'd like to do a full sweep

@@ -42,26 +42,20 @@ const verify = async (utln: string, code: number) => {

// No email has been sent for this utln
if (result.rowCount === 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more of a syntac standardization idea, but I was thinking we could do
result.rowCount === 0 && return apiUtils...
for these kinda things? It would really clean up lots of code that's lots of conditional chaining

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that cleaner in general? I guess I don't see that as more readable

@mgreenw mgreenw changed the title BIG server clean & API response schema standardization 1. BIG server clean & API response schema standardization Feb 5, 2019
Copy link
Collaborator

@JacobJaffe JacobJaffe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More incremental comments

@@ -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(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this happen in the finally? It's dangerous to put anything after the s3Delete, because theoretically any additional code could trigger the rollback, but s3 would have been changed.
Actually, for this reason I think step 3 and 4 should also be switched.

};

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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the Number.parseInt should be abstracted to a photoUtils function if we pass the same constant base 10 to it every time.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Params.photoId comes in as a string. I am about to refactor this endpoint to be MUCH simpler using a new way of ordering photos using a trigger. This will remove the need for the photoId to come in as a number and instead allow it to come in as a string. Therefore, I'd prefer not to add a utility here as it will soon be removed

status: codes.GET_PHOTO__NOT_FOUND,
});
// $FlowFixMe I'm really not sure what's going on here...it's only HERE
return apiUtils.status(codes.GET_PHOTO__NOT_FOUND).noData();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spent a little time debugging this. I think the root of this is the implicit types returned by functions; feels like it should work, perhaps related to:
facebook/flow#5294

I tested out a solution there by explicitly typing the return type (which is safe, as flow does make sure it's a valid return type).

    const foo: { body: { status: string }, statusCode: number } = apiUtils
      .status(codes.GET_PHOTO__NOT_FOUND)
      .noData();
    return foo;

Worth looking into later the deeper cause of this, but how about for now at least keeping this with a TODO comment so that the export here is well typed

Copy link
Collaborator

@JacobJaffe JacobJaffe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see comments

return apiUtils.status(400).json({
status: codes.REORDER_PHOTOS__MISMATCHED_IDS,
});
return apiUtils.status(codes.REORDER_PHOTOS__MISMATCHED_IDS).noData();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we return or log the mismatches?
edit: made a task

@@ -75,8 +75,74 @@ function getFieldTemplates(definedFields: Array<[string, any]>) {
};
}

const DefaultProfileOptions = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments please

@@ -11,7 +11,8 @@ 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please elaborate

@JacobJaffe JacobJaffe merged commit a9083ed into master Feb 8, 2019
@mgreenw mgreenw deleted the max.server.api-return-schema-profiles branch May 5, 2019 00:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants