diff --git a/src/config/app.json b/src/config/app.json index 15341e0..d2e6cd4 100644 --- a/src/config/app.json +++ b/src/config/app.json @@ -50,6 +50,13 @@ } } }, + "buckets": { + "profile": "tagfer-inc_profile-images" + }, + "imageFormat": { + "image/jpeg": "jpg", + "image/png": "png" + }, "dbPath": { "users": "users", "profiles": "profiles" diff --git a/src/config/router.js b/src/config/router.js index 2701a77..acd5407 100644 --- a/src/config/router.js +++ b/src/config/router.js @@ -27,6 +27,7 @@ function router(app) { // Profile Endpoints app.post('/profiles/:profileNumber', ProfileHandlers.updateUserProfile); app.get('/profiles/:profileNumber', ProfileHandlers.getUserProfile); + app.put('/profiles/uploadImage/:profileNumber', ProfileHandlers.updateUserProfileImage); } module.exports = router; \ No newline at end of file diff --git a/src/ups/profiles/dao.js b/src/ups/profiles/dao.js index 4d3d1f3..77a78b5 100644 --- a/src/ups/profiles/dao.js +++ b/src/ups/profiles/dao.js @@ -1,4 +1,6 @@ const database = require('firebase-admin').database(); +const utils = require('../utils/utils'); +const appConfig = require('../../config/app.json'); /** * Blind initializing function for a new user's default profile @@ -6,8 +8,8 @@ const database = require('firebase-admin').database(); * @param {string} tagferId */ function createInitialProfiles(profileObj, tagferId) { - //persist profile data to firebase - return database.ref(`/profiles/${tagferId}/profile1`).set(profileObj); + //persist profile data to firebase + return database.ref(`/profiles/${tagferId}/profile1`).set(profileObj); } /** @@ -18,8 +20,8 @@ function createInitialProfiles(profileObj, tagferId) { * @returns {Boolean} Boolean result of whether the */ function updateProfile(profileObj, profileNumber, tagferId) { - //persist profile data to firebase - return database.ref(`/profiles/${tagferId}/profile${profileNumber}`).set(profileObj); + //persist profile data to firebase + return database.ref(`/profiles/${tagferId}/profile${profileNumber}`).set(profileObj); } /** @@ -29,13 +31,32 @@ function updateProfile(profileObj, profileNumber, tagferId) { * @returns {Object} Profile object containg information for a specific user's profile */ function getProfile(profileNumber, tagferId) { - return database.ref(`/profiles/${tagferId}/profile${profileNumber}`).once('value').then(function(snapshot) { - return (snapshot.exists() ? snapshot.val() : {}); - }); + return database.ref(`/profiles/${tagferId}/profile${profileNumber}`).once('value').then(function (snapshot) { + return (snapshot.exists() ? snapshot.val() : {}); + }); +} + +/** + * Updates a user's profile image + * @param {object} profileImageData JSON object containing all image data for a profile captured from the frontend + * @param {number} profileNumber Number used to identify which profile a user wants to update/add image to + * @param {string} tagferId tagferId obtained by extracting from authorization header + * @returns {object} Object containing a Promise that contains the result of uploading profile image, and the image url | {promise, imageURL} + */ + +async function updateProfileImage(profileImageData, profileNumber, tagferId) { + //persist profile image data to firebase storage + try { + const downloadURL = await utils.uploadImage(profileImageData, `${tagferId}-profile${profileNumber}`, appConfig.buckets.profile); + return { promise: database.ref(`/profiles/${tagferId}/profile${profileNumber}/photoURL`).set(downloadURL), imageURL: downloadURL }; + } catch (error) { + throw error; + } } module.exports = { - updateProfile, - createInitialProfiles, - getProfile + updateProfile, + createInitialProfiles, + updateProfileImage, + getProfile }; diff --git a/src/ups/profiles/handlers.js b/src/ups/profiles/handlers.js index 6085056..2e64c3f 100644 --- a/src/ups/profiles/handlers.js +++ b/src/ups/profiles/handlers.js @@ -1,9 +1,8 @@ const profileDao = require('./dao'); const authDao = require('../auth/dao'); const utils = require('../utils/utils'); -const errors = require('../../config/errors'); +const errors = require('../../config/errors'); const http = require('../../config/http'); -const _ = require('lodash'); // Handlers /** @@ -13,44 +12,69 @@ const _ = require('lodash'); * @param {Object} res {result: Boolean} | {error: String} */ async function updateUserProfile(req, res) { - const profileObj = req.body; - const profileNumber = req.params.profileNumber; + const profileObj = req.body; + const profileNumber = req.params.profileNumber; - if (!utils.isProfileNumberValid(profileNumber, res)) { - return; - } - const sessionId = utils.getSessionIdFromAuthHeader(req, res); - try { - const tagferId = authDao.getSession(sessionId).tagferId; - profileDao.updateProfile(profileObj, profileNumber, tagferId).then( () => { - res.status(http.CREATED).json({}) - }).catch( (error) => { - res.status(http.INTERNAL_SERVER_ERROR).json({error: errors.APP_FIREBASE_DATABASE_ERROR}) - }); - } catch (error) { - res.status(http.UNAUTHORIZED).json({error}) - } + if (!utils.isProfileNumberValid(profileNumber, res)) { + return; + } + const sessionId = utils.getSessionIdFromAuthHeader(req, res); + try { + const tagferId = authDao.getSession(sessionId).tagferId; + profileDao.updateProfile(profileObj, profileNumber, tagferId).then(() => { + res.status(http.CREATED).json({}); + }).catch((error) => { + res.status(http.INTERNAL_SERVER_ERROR).json({ error: errors.APP_FIREBASE_DATABASE_ERROR }); + }); + } catch (error) { + res.status(http.UNAUTHORIZED).json({ error }); + } +} + +/** + * Endpoints: PUT profiles/uploadImage/:profileNumber + * Updates user's profile with a profile image based on profile number given. + * This endpoint is called seperately in order to add an image to a profile. + * @param {Object} req {profileNumber} + * @param {Object} res + */ +async function updateUserProfileImage(req, res) { + const profileImageObj = req.body; + const sessionId = utils.getSessionIdFromAuthHeader(req, res); + + try { + const tagferId = authDao.getSession(sessionId).tagferId; + const result = await profileDao.updateProfileImage(profileImageObj, req.params.profileNumber, tagferId); + result.promise.then(() => { + res.status(http.OK).json({ imageURL: result.imageURL }); + }).catch((error) => { + res.status(http.INTERNAL_SERVER_ERROR).json({ error }); + }); + } catch (error) { + res.status(http.BAD_REQUEST).json({ error }); + } } async function getUserProfile(req, res) { - const profileNumber = req.params.profileNumber; - if (!utils.isProfileNumberValid(profileNumber)) { - return; - } - const sessionId = utils.getSessionIdFromAuthHeader(req, res); - try { - const tagferId = authDao.getSession(sessionId).tagferId; - profileDao.getProfile(profileNumber,tagferId).then((profile) => { - res.status(http.OK).json({profile}) - }).catch(error => { - res.status(http.INTERNAL_SERVER_ERROR).json({error: error.code}) - }) - } catch (error) { - res.status(http.UNAUTHORIZED).json({ error }) - } + const profileNumber = req.params.profileNumber; + if (!utils.isProfileNumberValid(profileNumber)) { + return; + } + const sessionId = utils.getSessionIdFromAuthHeader(req, res); + try { + const tagferId = authDao.getSession(sessionId).tagferId; + profileDao.getProfile(profileNumber, tagferId).then((profile) => { + res.status(http.OK).json({ profile }); + }).catch(error => { + res.status(http.INTERNAL_SERVER_ERROR).json({ error: error.code }); + }); + } catch (error) { + res.status(http.UNAUTHORIZED).json({ error }); + } } module.exports = { - updateUserProfile, - getUserProfile -} \ No newline at end of file + updateUserProfile, + updateUserProfileImage, + getUserProfile +}; \ No newline at end of file diff --git a/src/ups/utils/utils.js b/src/ups/utils/utils.js index 9c2a3f5..eae19d1 100644 --- a/src/ups/utils/utils.js +++ b/src/ups/utils/utils.js @@ -1,9 +1,9 @@ const OAuth = require('oauth-1.0a'); const crypto = require('crypto'); - const appConfig = require('../../config/app.json'); const http = require('../../config/http'); const errors = require('../../config/errors'); +const firebase = require('firebase-admin'); /** * Verifies if the request is valid by checking if the request has the right app secret. @@ -71,10 +71,37 @@ function createOAuthHeader(request, app) { return oauth.toHeader(oauth.authorize(request, app.token)); } +/** + * + * @param {*} image as raw bytes + * @param {*} path to saving image in firebase + * @param {*} bucketName bucketName + * + * @returns {*} imageURL + */ +async function uploadImage(imageData, path, bucketName) { + try { + const bucket = firebase.storage().bucket(`gs://${bucketName}`); + + const file = bucket.file(`${path}.${appConfig.imageFormat[imageData.metaData.contentType]}`); + const imageBuffer = Buffer.from(imageData.base64Data, 'base64'); + + await file.save(imageBuffer); + + const fileMetaData = await file.getMetadata(); + + return fileMetaData[0].mediaLink; + + } catch (error) { + throw error; + } +} + module.exports = { isAppSecretValid, isBodyValid, getSessionIdFromAuthHeader, isProfileNumberValid, - createOAuthHeader + createOAuthHeader, + uploadImage }; \ No newline at end of file