Skip to content

Commit

Permalink
Merge pull request #12 from wolox-training/buy-album
Browse files Browse the repository at this point in the history
Buy album
  • Loading branch information
lnmedrano committed Jul 23, 2019
2 parents 091ba23 + 02f5a49 commit 8c5fc75
Show file tree
Hide file tree
Showing 13 changed files with 171 additions and 12 deletions.
2 changes: 2 additions & 0 deletions app/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const DEFAULT_ERROR = 500,
DB_ERROR = 503,
VALIDATION_ERROR = 401,
TOKEN_ERROR = 500,
PERMISSION_ERROR = 401,
HASH_ERROR = 500;

exports.defaultError = message => createError(message, DEFAULT_ERROR);
Expand All @@ -16,4 +17,5 @@ exports.apiError = message => createError(message, API_ERROR);
exports.dbError = message => createError(message, DB_ERROR);
exports.validationError = message => createError(message, VALIDATION_ERROR);
exports.tokenError = message => createError(message, TOKEN_ERROR);
exports.permissionError = message => createError(message, PERMISSION_ERROR);
exports.hashError = message => createError(message, HASH_ERROR);
6 changes: 4 additions & 2 deletions app/graphql/albums/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const { queries, schema: queriesSchema } = require('./queries'),
{ typeResolvers } = require('./resolvers');
{ typeResolvers } = require('./resolvers'),
{ mutations, schema: mutationsSchema } = require('./mutations');

module.exports = {
queries,
mutations,
typeResolvers,
schemas: [queriesSchema]
schemas: [queriesSchema, mutationsSchema]
};
24 changes: 24 additions & 0 deletions app/graphql/albums/mutations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { gql } = require('apollo-server'),
{ createPurchase, album } = require('./resolvers'),
{ permissionError } = require('../../errors'),
{ getEmailFromToken } = require('../../helpers/token'),
{ user: User } = require('../../models');

module.exports = {
mutations: {
buyAlbum: (_, { albumToBuy }, context) => {
if (context.tokenValidated) {
return getEmailFromToken(context.token)
.then(email => User.getByEmail(email))
.then(user => createPurchase(albumToBuy.albumId, user.id).then(albumId => album(albumId)));
}
throw permissionError('User does not have permission');
}
},

schema: gql`
extend type Mutation {
buyAlbum(albumToBuy: AlbumInput!): Album
}
`
};
16 changes: 15 additions & 1 deletion app/graphql/albums/resolvers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const { getAlbumById, requestAlbumPhotos, getAlbums, getAlbumsFiltered } = require('../../services/typicode'),
logger = require('../../logger');
logger = require('../../logger'),
{ purchase: Purchase } = require('../../models'),
{ validationError } = require('../../errors');

exports.album = albumId => {
logger.info(`Requesting album with id ${albumId}`);
Expand Down Expand Up @@ -28,6 +30,18 @@ exports.albums = (offset, limit, orderBy, filterBy) => {
});
};

exports.createPurchase = (albumId, userId) => {
logger.info(`Purchasing album with id ${albumId} for user with id ${userId}`);
return getAlbumById(albumId).then(() =>
Purchase.createPurchase(albumId, userId).then(([purchase, created]) => {
if (created) {
return purchase.albumId;
}
throw validationError('User already bought that album');
})
);
};

const photos = album => {
logger.info(`Requesting photos of album with id ${album.id}`);
return requestAlbumPhotos(album.id);
Expand Down
3 changes: 2 additions & 1 deletion app/graphql/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ module.exports = makeExecutableSchema({
...albums.queries
},
Mutation: {
...users.mutations
...users.mutations,
...albums.mutations
},
Subscription: {
...users.subscriptions
Expand Down
3 changes: 3 additions & 0 deletions app/graphql/inputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ module.exports = gql`
email: String!
password: String!
}
input AlbumInput {
albumId: Int!
}
`;
34 changes: 33 additions & 1 deletion app/helpers/token.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const jsrasign = require('jsrsasign'),
config = require('../../config').common.token,
logger = require('../logger');
logger = require('../logger'),
{ tokenError } = require('../errors'),
{ user: User } = require('../models');

exports.createToken = sub => {
logger.info('Creating token');
Expand All @@ -12,3 +14,33 @@ exports.createToken = sub => {
payload.exp = payload.nbf + config.sessionTime;
return jsrasign.jws.JWS.sign(config.algorithm, header, payload, config.pass);
};

const getEmailFromToken = token =>
new Promise(resolve => {
resolve(jsrasign.b64toutf8(token.split('.')[1]));
})
.then(jsonString => new Promise(resolve => resolve(JSON.parse(jsonString).sub)))
.catch(error => tokenError(error));

const validateWithEmail = (token, email) =>
User.getByEmail(email).then(foundUser => {
if (foundUser) {
return jsrasign.jws.JWS.verifyJWT(token, config.pass, {
alg: [config.algorithm],
sub: [email]
});
}
return false;
});

const resolveValidation = validated => validated;

exports.validateToken = token => {
logger.info('Validating token');
return getEmailFromToken(token)
.then(email => validateWithEmail(token, email))
.then(validated => resolveValidation(validated))
.catch(() => false);
};

exports.getEmailFromToken = getEmailFromToken;
27 changes: 27 additions & 0 deletions app/models/purchase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { dbError } = require('../errors');

module.exports = (sequelize, DataTypes) => {
const Purchase = sequelize.define(
'purchase',
{
albumId: {
type: DataTypes.INTEGER,
allowNull: false
},
userId: {
type: DataTypes.INTEGER,
allowNull: false
}
},
{
paranoid: true,
underscored: true
}
);

Purchase.createPurchase = (albumId, userId) =>
Purchase.findOrCreate({ where: { albumId, userId }, default: {} }).catch(error => {
throw dbError(error.message);
});
return Purchase;
};
24 changes: 24 additions & 0 deletions migrations/migrations/20190719160403-create-purchases.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) =>
queryInterface.createTable('purchases', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
albumId: {
type: Sequelize.INTEGER,
allowNull: false
},
userId: {
type: Sequelize.INTEGER,
allowNull: false
},
created_at: Sequelize.DATE,
updated_at: Sequelize.DATE,
deleted_at: Sequelize.DATE
}),
down: queryInterface => queryInterface.dropTable('purchases')
};
17 changes: 13 additions & 4 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ const { ApolloServer } = require('apollo-server'),
config = require('./config'),
migrationsManager = require('./migrations'),
logger = require('./app/logger'),
schema = require('./app/graphql');
schema = require('./app/graphql'),
{ validateToken } = require('./app/helpers/token');

const port = config.common.api.port || 8080;

Expand All @@ -14,9 +15,17 @@ migrationsManager
enabled: !!config.common.rollbar.accessToken,
environment: config.common.rollbar.environment || config.environment
}); */
new ApolloServer({ schema }).listen(port).then(({ url, subscriptionsUrl }) => {
logger.info(`🚀 Server ready at ${url}`);
logger.info(`🚀 Subscriptions ready at ${subscriptionsUrl}`);
new ApolloServer({
schema,
context: ({ req }) => {
const token = req.headers.token || '';
return validateToken(token).then(validated => ({ tokenValidated: validated, token }));
}
})
.listen(port)
.then(({ url, subscriptionsUrl }) => {
logger.info(`🚀 Server ready at ${url}`);
logger.info(`🚀 Subscriptions ready at ${subscriptionsUrl}`);
})
)
.catch(logger.error);
19 changes: 18 additions & 1 deletion test/albums/graphql.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,21 @@ const albums = (offset, limit, orderBy = null, filterBy = null) => gql`
}
`;

module.exports = { album, albums };
const buyAlbum = (albumToBuy, token) => ({
mutation: gql`
mutation buyAlbum($albumToBuy: AlbumInput!) {
buyAlbum(albumToBuy: $albumToBuy) {
id
title
}
}
`,
variables: { albumToBuy },
options: {
context: {
headers: { token }
}
}
});

module.exports = { album, albums, buyAlbum };
6 changes: 5 additions & 1 deletion test/server.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ const { createTestClient } = require('apollo-server-testing'),
{ ApolloServer } = require('apollo-server'),
schema = require('../app/graphql');

const { query: _query, mutate } = createTestClient(new ApolloServer({ schema }));
const { query: _query, mutate } = createTestClient(
new ApolloServer({
schema
})
);

const query = params => _query({ query: params });

Expand Down
2 changes: 1 addition & 1 deletion test/users/mutations.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ const { mutate } = require('../server.spec'),
correctEmail = 'useremail@wolox.com.ar',
correctFirstName = 'fn',
correctLastName = 'ln',
gmailEmail = 'email@gmail.com',
correctPassword = 'password',
gmailEmail = 'email@gmail.com',
shortPassword = 'pass',
passwordWithDots = 'p.a.s.s.w.o.r.d';

Expand Down

0 comments on commit 8c5fc75

Please sign in to comment.