import 'isomorphic-fetch'; import { transaction } from 'objection'; import * as uuid from 'node-uuid'; import { User } from 'iworks-db-model'; import { sign } from 'jsonwebtoken'; import { omit, pick } from 'lodash'; import { sendNotification } from '../push'; import * as solar from '../helpers/solarStaff'; import { IUser, IUserLogin, ILoggedUser, IUserUpdatePwd, ILinkCardData, ILinkedCardData, IUserCardSelector, IUserCard, PaymentMethod, IUserPaymenMethod, IUserLoginMobile, } from '../model/user'; import config from '../config'; import { GraphQLError } from 'graphql'; import { AuthenticationError } from '../errors'; import { registerDevice } from '../device/deviceRepository'; import { IDevice } from '../model/device'; const { JWT_SECRET } = config; interface UserData { id: string; login: string; } export async function linkUserUserToPaymentSystem(id: string):Promise { let users : any[] = []; users = await User .query() .where('id', id); if (!users.length) { throw new Error(`User with id ${id} not found`); } const worker : solar.IWorkerCreateData = { first_name: users[0].firstName, last_name: users[0].lastName, email: users[0].email, phone: users[0].phone, specialization: solar.Specializations.PROGRAMMER, country: solar.Countries.RUSSIA, send_message: solar.SendMessage.YES, }; let solarUserResponse; try { solarUserResponse = await solar.workerCreate(worker); } catch (err) { throw new Error(err.message); } const dataToInsert: Partial = { paymentInfo: { externalUserId: solarUserResponse.response.id, activated: true, paymentMethod: PaymentMethod.QIWI, cards: [], }, }; return await User .query().patchAndFetchById(id, dataToInsert); } export async function deleteUser(id: string):Promise { const knex = User.knex(); return transaction(knex, async (trx) => { const user: any = await getUserByField('id', id, trx); if (user.status === User.STATUS_ACTIVE) { throw new Error('Cannot delete active user'); } return await User .query(trx).delete().where('id', id); }); } export async function setDefaultUserCard(data: IUserCardSelector):Promise { const knex = User.knex(); return transaction(knex, async (trx) => { const user: any = await getUserByField('email', data.email, trx); if (!user.paymentInfo) { user.paymentInfo = {}; } if (!user.paymentInfo.cards) { user.paymentInfo.cards = []; } user.paymentInfo.cards = user.paymentInfo.cards.map((card: IUserCard) => { card.isDefault = card.id === data.cardId; return card; }); const dataToInsert: Partial = { paymentInfo: user.paymentInfo, }; return await User .query(trx).patchAndFetchById(user.id, dataToInsert); }); } export async function setUserPaymentMethod(data: IUserPaymenMethod):Promise { const knex = User.knex(); return transaction(knex, async (trx) => { const user: any = await getUserByField('email', data.email, trx); if (!user.paymentInfo) { user.paymentInfo = {}; } user.paymentInfo.paymentMethod = data.paymentMethod; const dataToInsert: Partial = { paymentInfo: user.paymentInfo, }; return await User .query(trx).patchAndFetchById(user.id, dataToInsert); }); } export async function updateUserCardStatus(data: IUserCardSelector):Promise { const knex = User.knex(); return transaction(knex, async (trx) => { const user: any = await getUserByField('email', data.email, trx); let solarResponse : any; try { solarResponse = await solar.cardStatus( { email: user.email }, data.cardId, ); } catch (err) { throw new Error(err.message); } if (!user.paymentInfo) { user.paymentInfo = {}; } if (!user.paymentInfo.cards) { user.paymentInfo.cards = []; } user.paymentInfo.cards = user.paymentInfo.cards.map((item : any) => { const cardDataToUpdate : IUserCard = { id: data.cardId, statusId: solarResponse.response.verify_status_id , statusName: solarResponse.response.verify_status, cardNumber: solarResponse.response.card_number, isDefault: item.isDefault || false, }; return item.id === data.cardId ? cardDataToUpdate : item; }); const dataToInsert: Partial = { paymentInfo: user.paymentInfo, }; return await User .query(trx).patchAndFetchById(user.id, dataToInsert); }); } export async function removeUserCard(data: IUserCardSelector):Promise { const knex = User.knex(); return transaction(knex, async (trx) => { const user : any = await getUserByField('email', data.email, trx); let solarResponse; let isExistsOnSolar = true; try { solarResponse = await solar.cardStatus( { email: user.email }, data.cardId, ); } catch (err) { if (err.status && err.status === 404) { // Or check instance of Error may be better isExistsOnSolar = false; } else { throw new Error(err.message); } } if (isExistsOnSolar) { try { solarResponse = await solar.cardRemove( { email: user.email }, data.cardId, ); } catch (err) { throw new Error(err.message); } } if (!user.paymentInfo) { user.paymentInfo = {}; } if (!user.paymentInfo.cards) { user.paymentInfo.cards = []; } let removedDefault = false; user.paymentInfo.cards = user.paymentInfo.cards.filter((item : any) => { if (item.id === data.cardId) { if (item.isDefault) { removedDefault = true; } return false; } return true; }); if (removedDefault && user.paymentInfo.cards.length > 0) { user.paymentInfo.cards[0].isDefault = true; } const dataToInsert: Partial = { paymentInfo: user.paymentInfo, }; return await User .query(trx).patchAndFetchById(user.id, dataToInsert); }); } export async function addUserCard(data: IUserCardSelector):Promise { const knex = User.knex(); return transaction(knex, async (trx) => { const user : any = await getUserByField('email', data.email, trx); let solarResponse; try { solarResponse = await solar.cardStatus( { email: user.email }, data.cardId, ); } catch (err) { throw new Error(err.message); } if (!user.paymentInfo || !user.paymentInfo.activated) { throw new Error('User not activated in payment system'); } if (!user.paymentInfo.cards) { user.paymentInfo.cards = []; } const newCard : IUserCard = { id: data.cardId, statusId: solarResponse.response.verify_status_id, statusName: solarResponse.response.verify_status, cardNumber: solarResponse.response.card_number, isDefault: user.paymentInfo.cards.length === 0 ? true : false, }; const cardsWithSameId = user.paymentInfo.cards.filter((item: any) => { return item.id === data.cardId; }); if (cardsWithSameId.length > 0) { throw new Error(`Card with id ${data.cardId} already exists in user cards`); } user.paymentInfo.cards.push(newCard); const dataToInsert: Partial = { paymentInfo: user.paymentInfo, }; return await User .query(trx).patchAndFetchById(user.id, dataToInsert); }); } async function getUserByField(field: string, value: string, trx: any):Promise { let users : any[] = []; users = await User .query(trx) .where(field, value); if (!users.length) { throw new Error(`User with ${field} ${value} not found`); } return users[0]; } export async function linkUserCard(data: ILinkCardData):Promise { const knex = User.knex(); return transaction(knex, async (trx) => { const user : any = await getUserByField('email', data.email, trx); let solarResponse; try { solarResponse = await solar.cardVerify( { email: user.email }, { currency: solar.Currencies.RUSSIAN_RUBLE, redirect_url: data.redirectUrl }, ); } catch (err) { throw new Error(err.message); } const result : ILinkedCardData = { cardId: solarResponse.response.card_id, terminalUrl: solarResponse.response.terminal_url, verifyStatusId: solarResponse.response.verify_status_id, }; return result; }); } export async function unlinkUserFromPaymentSystem(id: string):Promise { const knex = User.knex(); return transaction(knex, async (trx) => { const user = await getUserByField('id', id, trx); if (user.paymentInfo === null || user.paymentInfo.externalUserId === null) { throw new Error(`User with id ${id} does not link to payment system`); } let solarUserResponse; try { solarUserResponse = await solar.workerRemove({ worker_id: user.paymentInfo.externalUserId }); } catch (err) { throw new Error(err.message); } const dataToInsert: Partial = { paymentInfo: { externalUserId: null, activated: false, }, }; return await User .query(trx).patchAndFetchById(id, dataToInsert); }); } export async function loginUser(data: IUserLoginMobile):Promise { let users : any[] = []; if (data.login) { users = await User .query() .where('login', data.login) .eager('roles'); } else if (data.phone) { users = await User .query() .where('phone', data.phone) .eager('roles'); } if (!users.length) { throw new AuthenticationError(); } const user = users[0]; const hashedPassword = await User.hashPassword(user.salt, data.pwd); if (hashedPassword !== user.pwd) { throw new AuthenticationError(`Wrong password`); } else { const jwt = sign( pick(user, ['id', 'login', 'roles', 'updated']), JWT_SECRET, { expiresIn: '1d' }, ); let device : IDevice = undefined; if (data.device) { const deviceData = { ...data.device, userId: user.id }; device = await registerDevice(deviceData) as IDevice; } return { jwt, device, id: user.id, firstName: user.firstName, lastName: user.lastName, login: user.login, roles: user.roles.map((role : any) => role.id), }; } } export function updateUser(data: Partial):Promise { const knex = User.knex(); return transaction(knex, async (trx) => { const userRoles : number[] = data.roles; const user = await User .query(trx) .findById(data.id); if (!user) { throw new Error(`User with id ${data.id} does not exist`); } await user .$relatedQuery('roles', trx) .unrelate(); if (userRoles && userRoles.length) { await user .$relatedQuery('roles', trx) .relate(userRoles); } const dataToUpdate: Partial = omit(data, ['roles']); return User .query(trx) .upsertGraph(dataToUpdate); }); } export async function createUser(data: IUser):Promise { const knex = User.knex(); const id = uuid.v4(); const date = (new Date()).toISOString(); const salt = await User.makeSalt(); const pwd = await User.hashPassword(salt, data.pwd); return transaction(knex, async (trx) => { const userRoles: number[] = data.roles; const existUser = await User.query(trx).where('login', data.login); if (existUser.length > 0) { throw new Error(`Can't create a new user. User with "${data.login}" already exists.`); } const dataToInsert: Partial = Object.assign( { id, salt, pwd, created: date, updated: date, }, omit(data, ['roles', 'pwd']), ); const user = await User .query(trx) .insertGraph(dataToInsert); if (userRoles && userRoles.length) { await user .$relatedQuery('roles', trx) .relate(userRoles); } return user; }); } export async function updateUserPwd(data: IUserUpdatePwd):Promise { // const notAdmin = context.user.roles.indexOf('AM') === -1; const { id, oldPwd, newPwd, newPwdRepeat } = data; /* if (notAdmin && !oldPwd || oldPwd.trim().length) { throw new Error(`Please insert your old password`); } */ if (newPwd.length < 4) { throw new Error(`Password length should be more or equel to 4`); } if (newPwd !== newPwdRepeat) { throw new Error(`New password and repeat password not the same`); } const knex = User.knex(); const date = (new Date()).toISOString(); const salt = await User.makeSalt(); const pwd = await User.hashPassword(salt, newPwd); const dataToInsert: Partial = { salt, pwd, updated: date, }; return await User .query().patchAndFetchById(id, dataToInsert); }