diff --git a/lib/customers.js b/lib/customers.js index 029a2fd1..c839b97f 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -161,24 +161,20 @@ function edit (id, data, userToken) { ] const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, _.omitBy(_.isNil, data))) if (_.isEmpty(filteredData)) return getCustomerById(id) - if (filteredData.front_camera_data || filteredData.id_card_photo_data) return getCustomerById(id) - const formattedData = enhanceEditedFields(filteredData, userToken) + const formattedData = enhanceEditedPhotos(enhanceEditedFields(filteredData, userToken)) const defaultDbData = { customer_id: id, created: new Date(), ...formattedData } - console.log(formattedData, 'FORMATED', defaultDbData, 'DEFAULT DB') + const cs = new Pgp.helpers.ColumnSet(_.keys(defaultDbData), { table: 'edited_customer_data' }) const onConflict = ' ON CONFLICT (customer_id) DO UPDATE SET ' + cs.assignColumns({ from: 'EXCLUDED', skip: ['customer_id', 'created'] }) const upsert = Pgp.helpers.insert(defaultDbData, cs) + onConflict - db.none(upsert) - .then(res => { - console.log(res) - return getCustomerById(id) - }) + return db.none(upsert) + .then(getCustomerById(id)) } /** @@ -202,6 +198,25 @@ function enhanceEditedFields (fields, userToken) { return fields } +/** + * Add *_path to edited photos fields + * + * @name enhanceEditedFields + * @function + * + * @param {object} fields Fields to be enhanced + * @returns {object} fields enhanced with *_path + */ + +function enhanceEditedPhotos (fields) { + return _.mapKeys((field) => { + if (_.includes(field, ['front_camera', 'id_card_photo'])) { + return field + '_path' + } + return field + }, fields) +} + /** * Remove the edited data from the db record * @@ -248,38 +263,27 @@ function deleteEditedData (id, data) { * @returns {object} path New photo path * */ -function replacePhoto (id, photo, photoType) { +async function updateEditedPhoto (id, photo, photoType) { + const newPatch = {} const baseDir = photoType === 'frontCamera' ? frontCameraBaseDir : idPhotoCardBasedir - const { createReadStream } = photo + const { createReadStream, filename } = photo const stream = createReadStream() - // workout the image hash - // i.e. 240e85ff2e4bb931f235985dd0134e459239496d2b5af6c5665168d38ef89b50 - const hash = crypto - .createHash('sha256') - .update(imageData) - .digest('hex') + const randomString = uuid.v4().toString() + '/' - // workout the image folder - // i.e. 24/0e/85 - const rpath = _.join(path.sep, _.map(_.wrap(_.join, ''), _.take(3, _.chunk(2, _.split('', hash))))) - - // i.e. ..//idphotocard/24/0e/85 - const dirname = path.join(idPhotoCardBasedir, rpath) + // i.e. ..62ed29c5-f37e-4fb7-95bb-c52d4a3738f7/filename.jpg + const rpath = path.join(randomString, filename) // create the directory tree if needed - _.attempt(() => makeDir.sync(dirname)) + _.attempt(() => makeDir.sync(path.join(baseDir, randomString))) - // i.e. ..//idphotocard/24/0e/85/240e85ff2e4bb931f235985dd01....jpg - const filename = path.join(dirname, hash + '.jpg') + // i.e. ..//idphotocard/62ed29c5-f37e-4fb7-95bb-c52d4a3738f7/filename.jpg + const pathName = path.join(baseDir, rpath) - // update db record patch - // i.e. { - // "idCardPhotoPath": "24/0e/85/240e85ff2e4bb931f235985dd01....jpg", - // "idCardPhotoAt": "now()" - // } - newPatch.idCardPhotoPath = path.join(rpath, hash + '.jpg') - newPatch.idCardPhotoAt = 'now()' + await stream.pipe(fs.createWriteStream(pathName)) + newPatch[photoType] = rpath + + return newPatch } const invalidateCustomerNotifications = (id, data) => { @@ -670,9 +674,9 @@ function getCustomersList (phone = null, name = null, address = null, id = null) */ function getCustomerById (id) { const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',') - const sql = `select id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_at, front_camera_override, - phone, sms_override, id_card_data, id_card_data_override, id_card_data_expiration, - id_card_photo_path, id_card_photo_override, us_ssn, us_ssn_override, sanctions, sanctions_at, + const sql = `select id, authorized_override, days_suspended, is_suspended, front_camera_at, front_camera_path, front_camera_at, front_camera_override, + phone, sms_override, id_card_data_at, id_card_data, id_card_data_override, id_card_data_expiration, + id_card_photo_at, id_card_photo_path, id_card_photo_override, us_ssn_at, us_ssn, us_ssn_override, sanctions, sanctions_at, sanctions_override, total_txs, total_spent, created as last_active, fiat as last_tx_fiat, fiat_code as last_tx_fiat_code, tx_class as last_tx_class, subscriber_info, custom_fields from ( @@ -680,8 +684,8 @@ function getCustomerById (id) { greatest(0, date_part('day', c.suspended_until - now())) as days_suspended, c.suspended_until > now() as is_suspended, c.front_camera_path, c.front_camera_at, c.front_camera_override, - c.phone, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration, - c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions, + c.phone, c.sms_override, c.id_card_data, c.id_card_data_at, c.id_card_data_override, c.id_card_data_expiration, + c.id_card_photo_path, c.id_card_photo_at, c.id_card_photo_override, c.us_ssn, c.us_ssn_at, c.us_ssn_override, c.sanctions, c.sanctions_at, c.sanctions_override, c.subscriber_info, t.tx_class, t.fiat, t.fiat_code, t.created, row_number() over (partition by c.id order by t.created desc) as rn, sum(case when t.id is not null then 1 else 0 end) over (partition by c.id) as total_txs, @@ -700,6 +704,10 @@ function getCustomerById (id) { where c.id = $2 ) as cl where rn = 1` return db.oneOrNone(sql, [passableErrorCodes, id]) + .then(customerData => { + return getEditedData(id) + .then(customerEditedData => selectLatestData(customerData, customerEditedData)) + }) .then(populateOverrideUsernames) .then(camelize) } @@ -709,10 +717,16 @@ function getCustomerById (id) { * * @param {String} id customer id * - * @returns {array} A single customer instance with the most recent data + * @returns {array} A single customer instance with the most recent edited data */ -function getManuallyEditedData (id) { +function getEditedData (id) { + const sql = `SELECT * FROM edited_customer_data WHERE customer_id = $1` + return db.oneOrNone(sql, [id]) + .then(_.omitBy(_.isNil)) +} +function selectLatestData (customerData, customerEditedData) { + return customerData } /** @@ -983,7 +997,7 @@ module.exports = { saveCustomField, removeCustomField, edit, - getManuallyEditedData, deleteEditedData, + updateEditedPhoto, updateTxCustomerPhoto } diff --git a/lib/new-admin/admin-server.js b/lib/new-admin/admin-server.js index 1f37969f..53f86ccc 100644 --- a/lib/new-admin/admin-server.js +++ b/lib/new-admin/admin-server.js @@ -9,7 +9,7 @@ const helmet = require('helmet') const nocache = require('nocache') const cookieParser = require('cookie-parser') const { ApolloServer, AuthenticationError } = require('apollo-server-express') -// const { graphqlUploadExpress } = require('graphql-upload') +const { graphqlUploadExpress } = require('graphql-upload') const _ = require('lodash/fp') const { asyncLocalStorage, defaultStore } = require('../async-storage') @@ -48,11 +48,12 @@ app.use(cleanUserSessions(USER_SESSIONS_CLEAR_INTERVAL)) app.use(computeSchema) app.use(findOperatorId) app.use(session) -// app.use(graphqlUploadExpress()) +app.use(graphqlUploadExpress()) const apolloServer = new ApolloServer({ typeDefs, resolvers, + uploads: false, schemaDirectives: { auth: AuthDirective }, diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index 5bfd42e4..2db8b153 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -27,9 +27,10 @@ const resolvers = { return customers.edit(customerId, editedData, token) }, replacePhoto: async (root, { customerId, photoType, newPhoto }, context) => { + const token = !!context.req.cookies.lid && context.req.session.user.id const photo = await newPhoto - return customers.replacePhoto(customerId, photoType, photo) - .then(() => customers.getCustomerById(customerId)) + return customers.updateEditedPhoto(customerId, photo, photoType) + .then(newPatch => customers.edit(customerId, newPatch, token)) }, deleteEditedData: (root, { customerId, customerEdit }) => { return customers.deleteEditedData(customerId, customerEdit) diff --git a/lib/new-admin/graphql/resolvers/scalar.resolver.js b/lib/new-admin/graphql/resolvers/scalar.resolver.js index 57d556ae..54782105 100644 --- a/lib/new-admin/graphql/resolvers/scalar.resolver.js +++ b/lib/new-admin/graphql/resolvers/scalar.resolver.js @@ -1,12 +1,13 @@ const { GraphQLDateTime } = require('graphql-iso-date') const { GraphQLJSON, GraphQLJSONObject } = require('graphql-type-json') - +const { GraphQLUpload } = require('graphql-upload') GraphQLDateTime.name = 'Date' const resolvers = { JSON: GraphQLJSON, JSONObject: GraphQLJSONObject, - Date: GraphQLDateTime + Date: GraphQLDateTime, + UploadGQL: GraphQLUpload } module.exports = resolvers diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js index f6d56d4d..bbb90aa0 100644 --- a/lib/new-admin/graphql/types/customer.type.js +++ b/lib/new-admin/graphql/types/customer.type.js @@ -12,7 +12,7 @@ const typeDef = gql` authorizedOverride: String daysSuspended: Int isSuspended: Boolean - newPhoto: Upload + newPhoto: UploadGQL photoType: String frontCameraPath: String frontCameraAt: Date @@ -23,7 +23,7 @@ const typeDef = gql` idCardData: JSONObject idCardDataOverride: String idCardDataExpiration: Date - idCardPhoto: Upload + idCardPhoto: UploadGQL idCardPhotoPath: String idCardPhotoOverride: String usSsn: String @@ -70,7 +70,7 @@ const typeDef = gql` input CustomerEdit { idCardData: JSONObject - idCardPhoto: Upload + idCardPhoto: UploadGQL usSsn: String } @@ -87,7 +87,7 @@ const typeDef = gql` removeCustomField(customerId: ID!, fieldId: ID!): CustomerCustomField @auth editCustomer(customerId: ID!, customerEdit: CustomerEdit): Customer @auth deleteEditedData(customerId: ID!, customerEdit: CustomerEdit): Customer @auth - replacePhoto(customerId: ID!, photoType: String, newPhoto: Upload): Customer @auth + replacePhoto(customerId: ID!, photoType: String, newPhoto: UploadGQL): Customer @auth } ` diff --git a/lib/new-admin/graphql/types/scalar.type.js b/lib/new-admin/graphql/types/scalar.type.js index 693adccb..c872c1d1 100644 --- a/lib/new-admin/graphql/types/scalar.type.js +++ b/lib/new-admin/graphql/types/scalar.type.js @@ -4,6 +4,7 @@ const typeDef = gql` scalar JSON scalar JSONObject scalar Date + scalar UploadGQL ` module.exports = typeDef diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js index 9785afa3..5acef0ba 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js @@ -85,13 +85,7 @@ const CustomerData = ({ const isEven = elem => elem % 2 === 0 - const getVisibleCards = _.filter( - elem => - !_.isEmpty(elem.fields) || - (!_.isNil(elem.children) && !_.isNil(elem.state)) - ) - - const getAvailableFields = _.filter(({ value }) => value !== '') + const getVisibleCards = _.filter(elem => elem.isAvailable) const schemas = { idScan: Yup.object().shape({ @@ -188,7 +182,7 @@ const CustomerData = ({ const cards = [ { - fields: getAvailableFields(idScanElements), + fields: idScanElements, title: 'ID Scan', titleIcon: , state: R.path(['idCardDataOverride'])(customer), @@ -198,21 +192,24 @@ const CustomerData = ({ deleteEditedData: () => deleteEditedData({ idCardData: null }), save: values => editCustomer({ idCardData: values }), validationSchema: schemas.idScan, - initialValues: initialValues.idScan + initialValues: initialValues.idScan, + isAvailable: !_.isNil(idData) }, { title: 'SMS Confirmation', titleIcon: , authorize: () => {}, reject: () => {}, - save: () => {} + save: () => {}, + isAvailable: false }, { title: 'Name', titleIcon: , authorize: () => {}, reject: () => {}, - save: () => {} + save: () => {}, + isAvailable: false }, { title: 'Sanctions check', @@ -221,10 +218,11 @@ const CustomerData = ({ authorize: () => updateCustomer({ sanctionsOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ sanctionsOverride: OVERRIDE_REJECTED }), - children: {sanctionsDisplay} + children: {sanctionsDisplay}, + isAvailable: !_.isNil(sanctions) }, { - fields: getAvailableFields(frontCameraElements), + fields: frontCameraElements, title: 'Front facing camera', titleIcon: , state: R.path(['frontCameraOverride'])(customer), @@ -247,10 +245,11 @@ const CustomerData = ({ ) : null, hasImage: true, validationSchema: schemas.frontCamera, - initialValues: initialValues.frontCamera + initialValues: initialValues.frontCamera, + isAvailable: !_.isNil(customer.frontCameraPath) }, { - fields: getAvailableFields(idCardPhotoElements), + fields: idCardPhotoElements, title: 'ID card image', titleIcon: , state: R.path(['idCardPhotoOverride'])(customer), @@ -271,10 +270,11 @@ const CustomerData = ({ ) : null, hasImage: true, validationSchema: schemas.idCardPhoto, - initialValues: initialValues.idCardPhoto + initialValues: initialValues.idCardPhoto, + isAvailable: !_.isNil(customer.idCardPhotoPath) }, { - fields: getAvailableFields(usSsnElements), + fields: usSsnElements, title: 'US SSN', titleIcon: , state: R.path(['usSsnOverride'])(customer), @@ -283,7 +283,8 @@ const CustomerData = ({ save: values => editCustomer({ usSsn: values.usSsn }), deleteEditedData: () => deleteEditedData({ usSsn: null }), validationSchema: schemas.usSsn, - initialValues: initialValues.usSsn + initialValues: initialValues.usSsn, + isAvailable: !_.isNil(customer.usSsn) } ] diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js index 936fd076..d75f5bef 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js @@ -75,7 +75,6 @@ const fieldUseStyles = makeStyles(fieldStyles) const EditableField = ({ editing, field, value, size, ...props }) => { const classes = fieldUseStyles() - console.log('FIELDDDDDDDD', field) const classNames = { [classes.field]: true, [classes.notEditing]: !editing @@ -174,7 +173,6 @@ const EditableCard = ({ {({ values, touched, errors, setFieldValue }) => (
- {console.log(values, touched, errors, 'FORMIK STATUS')}
diff --git a/package-lock.json b/package-lock.json index 0e4cd28b..5092830b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11479,6 +11479,30 @@ "resolved": "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz", "integrity": "sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==" }, + "graphql-upload": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/graphql-upload/-/graphql-upload-12.0.0.tgz", + "integrity": "sha512-ovZ3Q7sZ17Bmn8tYl22MfrpNR7nYM/DUszXWgkue7SFIlI9jtqszHAli8id8ZcnGBc9GF0gUTNSskYWW+5aNNQ==", + "requires": { + "busboy": "^0.3.1", + "fs-capacitor": "^6.2.0", + "http-errors": "^1.8.0", + "isobject": "^4.0.0", + "object-path": "^0.11.5" + }, + "dependencies": { + "fs-capacitor": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/fs-capacitor/-/fs-capacitor-6.2.0.tgz", + "integrity": "sha512-nKcE1UduoSKX27NSZlg879LdQc94OtbOsEmKMN2MBNudXREvijRKx2GEBsTMTfws+BrbkJoEuynbGSVRSpauvw==" + }, + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==" + } + } + }, "graphql-ws": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.1.0.tgz", diff --git a/package.json b/package.json index 3f94362e..030bf13b 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "graphql-iso-date": "^3.6.1", "graphql-tools": "^7.0.2", "graphql-type-json": "^0.3.1", + "graphql-upload": "12.0.0", "helmet": "^3.8.1", "inquirer": "^5.2.0", "json2csv": "^5.0.3",