Advertisement
Guest User

Untitled

a guest
May 10th, 2018
134
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /// <reference path="../../typings/index.d.ts" />
  2.  
  3. import * as path from 'path';
  4. import app from '../app';
  5. import * as winston from 'winston';
  6. import * as async from 'async';
  7. import * as waterfall from 'async-waterfall';
  8. import { OfferingStatus } from 'aws-sdk/clients/devicefarm';
  9. import * as Promise from 'bluebird';
  10. import * as _ from 'lodash';
  11. import * as moment from 'moment';
  12. import { type } from 'os';
  13. import * as ejs from 'ejs';
  14.  
  15. import { ImpressionsSummaryQuery, TopCampaignsQuery, TopRoutesQuery } from './campaign.queries';
  16.  
  17. import { CampaignConst } from './campaign-const';
  18. import CampaignStatus = CampaignConst.CampaignStatus;
  19. import CampaignActions = CampaignConst.CampaignActions;
  20. import CampaignStatisticsPeriod = CampaignConst.CampaignStatisticsPeriod;
  21.  
  22. import { AdConst } from './ad-const';
  23. import AdStatus = AdConst.AdStatus;
  24. import AdActions = AdConst.AdActions;
  25.  
  26. import { WorkFlowStateMachine } from '../module/workflow-state';
  27. import StateAction = WorkFlowStateMachine.Action;
  28. import State = WorkFlowStateMachine.State;
  29. import StateMachine = WorkFlowStateMachine.StateMachine;
  30. import WorkflowResults = WorkFlowStateMachine.Results;
  31.  
  32. import BadRequest from '../errors/BadRequest';
  33. import NotFound from '../errors/NotFound';
  34. import InvalidCampaign from '../errors/InvalidCampaign';
  35. import RemoteMethod from '../helpers/RemoteMethod';
  36. import { Utils } from '../helpers/utils';
  37.  
  38. const ObjectId = require('mongodb').ObjectId;
  39.  
  40. const appSettings = require('../app.settings');
  41.  
  42. /* CAMPAIGN WORKFLOW */
  43. /*Once a Campaign is Submitted, Advertiser can Run the Campaign.
  44.  Once a Campaign is Running Advertiser can Stop the Campaign.
  45.  After Campaign is Stopped, Advertiser can Run it again.
  46.  A Campaign should be Ended when the defined Flight period runs out. */
  47.  
  48. export = (Campaign: any) => {
  49.  
  50.   Campaign.validatesUniquenessOf('title', { scopedTo: ['advertiserId'] });
  51.  
  52.   // Each time the Campaign is retrieved by Front-end, validation on flight period is needed
  53.   // If period expired, Campaign status should automatically change to -> ended
  54.   let client = app.get('client');
  55.  
  56.   Campaign.afterRemote('find', (ctx, instance, next) => {
  57.     // Counter is needed as an indicator when to call fn next() to proceed with find method
  58.     let counter = 0;
  59.  
  60.     if (ctx.args &&
  61.       ctx.args.filter &&
  62.       ctx.args.filter.include &&
  63.       ctx.args.filter.include.lastIndexOf('campaignStatistics') > -1) {
  64.  
  65.       let promise = [];
  66.       instance.forEach(campaign => {
  67.         promise.push(
  68.           app.models.campaignStatistics.findOne({
  69.             where: {
  70.               campaignId: campaign.id,
  71.               periodType: CampaignStatisticsPeriod.AllTime
  72.             }
  73.           })
  74.             .then(x => {
  75.               if (!x) {
  76.                 return createStatistics(campaign, CampaignStatisticsPeriod.AllTime);
  77.               }
  78.               return x;
  79.             })
  80.             .then(entity => {
  81.               entity.days = Campaign.getDays(campaign);
  82.               campaign.__data.campaignStatistics = entity;
  83.             })
  84.         );
  85.       });
  86.  
  87.       Promise.all(promise).then(values => {
  88.         next();
  89.       }).catch(err => {
  90.         next(err);
  91.       });
  92.     } else {
  93.       next();
  94.     }
  95.   });
  96.  
  97.   Campaign.beforeRemote('create', (ctx, model, next) => {
  98.     ctx.req.body._status = {
  99.       status: CampaignStatus.Draft,
  100.       lastChangeDate: moment.utc()
  101.     };
  102.     let contextData = prepareStateMachineContext(ctx.req);
  103.     ctx.req.body.advertiserId = contextData.user.advertiserId;
  104.     validateCampaign(ctx.req.body).then(res => {
  105.  
  106.       ////NOTE: on create check if Edit action is allowed...
  107.       initializeStateMachineByInstance(ctx.req.body, contextData)
  108.         .then(machine => {
  109.           return machine.isActionAllowed(CampaignActions.Edit, contextData);
  110.         })
  111.         .then(allowed => {
  112.           if (!allowed) {
  113.             let error = new BadRequest(BadRequest.AD_BAD_REQUEST);
  114.             next(error);
  115.           } else {
  116.             next();
  117.           }
  118.         })
  119.         .catch(err => {
  120.           return next(err);
  121.         });
  122.     })
  123.       .catch(err => {
  124.         next(err);
  125.       });
  126.   });
  127.  
  128.   Campaign.beforeRemote('**', (ctx, instance, next) => {
  129.     if (ctx.methodString === 'campaign.replaceById' ||
  130.       ctx.methodString === 'campaign.patchAttributes') {
  131.       /////Check if update is allowed.
  132.       updateCampaign(ctx)
  133.         .then((result) => {
  134.           return validateCampaign(ctx.req.body).then(res => {
  135.             next();
  136.             return null;
  137.           })
  138.             .catch((err) => {
  139.               return next(new Error(err));
  140.             });
  141.         })
  142.         .catch((err) => {
  143.           return next(err);
  144.         });
  145.     } else {
  146.       next();
  147.     }
  148.   });
  149.  
  150.   const validateCampaign = (campaign: any): Promise => {
  151.  
  152.     return new Promise((resolve, reject) => {
  153.  
  154.       let startDate = moment(campaign.startDate);
  155.       let endDate = moment(campaign.endDate);
  156.       let maxEndDate = moment(campaign.startDate).add(appSettings.campaign.maxPeriodLength.amount, appSettings.campaign.maxPeriodLength.units);
  157.  
  158.       if (!validateUrls(campaign)) {
  159.         reject(new InvalidCampaign('Content URL-s should be from the same origin.'));
  160.         return;
  161.       } else if (endDate.isBefore(startDate)) {
  162.         let error = new InvalidCampaign('Start date must be before end date.');
  163.         reject(error);
  164.         return;
  165.       } else if (endDate.isAfter(maxEndDate)) {
  166.         let error = new InvalidCampaign('Maximum campaign duration is ' + appSettings.campaign.maxPeriodLength.amount + ' ' + appSettings.campaign.maxPeriodLength.units);
  167.         reject(error);
  168.         return;
  169.       } else if (!campaign.isFixedInventory && !campaign.totalBudget) {
  170.         // for standard campaign total budget is required, for fixed inv it is not
  171.         let error = new InvalidCampaign('Total budget is required');
  172.         reject(error);
  173.       } else if (campaign.maxDailyBudget && campaign.maxCPI > campaign.maxDailyBudget) {
  174.         let error = new InvalidCampaign('Maximum CPI can not be greater than daily budget');
  175.         reject(error);
  176.       } else if (campaign.totalBudget && campaign.maxCPI > campaign.totalBudget) {
  177.         let error = new InvalidCampaign('Maximum CPI can not be greater than total budget.');
  178.         reject(error);
  179.       } else if (campaign.totalBudget && campaign.maxDailyBudget && campaign.maxDailyBudget > campaign.totalBudget) {
  180.         let error = new InvalidCampaign('Daily budget can not be greater than total budget.');
  181.         reject(error);
  182.       } else if (campaign.maxCPI && campaign.maxCPI < 0.1) {
  183.         let error = new InvalidCampaign('Maximum CPI can not be less than 0.1.');
  184.         reject(error);
  185.       } else {
  186.         Promise.join(
  187.           app.models.advertiser.findById(campaign.advertiserId),
  188.           app.models.campaignStatistics.findOne({
  189.             where: {
  190.               campaignId: campaign.id,
  191.               periodType: CampaignStatisticsPeriod.AllTime
  192.             }
  193.           })).spread((advertiser, state) => {
  194.             if (state && state.impressions && campaign.impressionsLimit && state.impressions > campaign.impressionsLimit) {
  195.               let error = new InvalidCampaign('Impressions limit exceeded.');
  196.               return reject(error);
  197.             } else if (campaign.isFixedInventory) {
  198.               if (!advertiser.premium) {
  199.                 let error = new InvalidCampaign('Fixed inventory campaigns can be created only by premium users.');
  200.                 return reject(error);
  201.               } else {
  202.                 return resolve();
  203.               }
  204.             } else {
  205.               return resolve();
  206.             }
  207.           })
  208.           .catch(err => {
  209.             return reject(err);
  210.           });
  211.       }
  212.     });
  213.   };
  214.  
  215.   const validateUrls = (campaign: any): boolean => {
  216.     let url = process.env.BEMAD_URL;
  217.     if (url && campaign && campaign.ads && campaign.ads._content) {
  218.       for (let content of campaign.ads._content) {
  219.         if (content && content.imageUrl && !content.imageUrl.startsWith(`${url}/api/containers/`)) {
  220.           return false;
  221.         }
  222.       }
  223.     }
  224.  
  225.     return true;
  226.   };
  227.  
  228.   const updateCampaign = (ctx: any): Promise => {
  229.     return new Promise((resolve, reject) => {
  230.       ///let bpad = app.models.bpad;
  231.       let authHeader = Utils.getRequestAuthHeaderSync(ctx.req);
  232.       let contextData = {
  233.         user: authHeader.user,
  234.         roles: authHeader.roles,
  235.         isadmin: authHeader.isAdminUser,
  236.         campaign: null,
  237.         ad: null,
  238.         advertiser: null
  239.       };
  240.  
  241.       Campaign.findById(ctx.req.params.id)
  242.         .then((campaign) => {
  243.           if (!campaign) {
  244.             let error = new NotFound(NotFound.CAMPAIGN_NOT_FOUND);
  245.             return reject(error);
  246.  
  247.           }
  248.  
  249.           ctx.req.body = _.assign({}, campaign.__data, ctx.req.body);
  250.           ctx.req.body._status = campaign.__data._status;
  251.           ctx.req.body.statusLog = campaign.__data.statusLog;
  252.  
  253.           return initializeStateMachineByInstance(ctx.req.body, contextData);
  254.           // return bpad.findById(campaign.ad.id).then(ad => {
  255.           //   contextData.ad = ad;
  256.           //   return contextData;
  257.           // });        
  258.         })
  259.         // .then((x) => {
  260.         //   let stateMachine = Campaign.workflowStateMachineDefinition();
  261.         //   return stateMachine.initialize(x.campaign._status.status, x.campaign, x);      
  262.         // })
  263.         .then(machine => {
  264.           return machine.isActionAllowed(CampaignActions.Edit, contextData);
  265.         })
  266.         .then(allowed => {
  267.           if (!allowed) {
  268.             let error = new BadRequest(BadRequest.CAMPAIGN_EDIT_NOT_ALLOWED);
  269.             reject(error);
  270.             return;
  271.           } else {
  272.             resolve();
  273.           }
  274.         })
  275.         .catch(err => {
  276.           reject(err);
  277.         });
  278.     });
  279.   };
  280.  
  281.   Campaign.getCampaignStatisticsTotal = (id, options) => {
  282.     return Promise.join(
  283.       Campaign.findById(id),
  284.       app.models.campaignStatistics.findOne({
  285.         where: {
  286.           campaignId: id,
  287.           periodType: CampaignStatisticsPeriod.AllTime
  288.         }
  289.       })
  290.     ).spread((campaign, state) => {
  291.       if (state) {
  292.         state.days = Campaign.getDays(campaign);
  293.         return {
  294.           avgCPI: state.avgCPI,
  295.           impressions: state.impressions,
  296.           costs: state.costs,
  297.           days: state.days,
  298.           campaignId: state.campaignId
  299.         };
  300.       }
  301.       return state;
  302.     });
  303.     // return app.models.campaignStatistics.findOne({
  304.     //     where: {
  305.     //       campaignId: id,
  306.     //       periodType: CampaignStatisticsPeriod.AllTime
  307.     //     }
  308.     //   });
  309.   };
  310.  
  311.   Campaign.afterRemote('**', (ctx, model, next) => {
  312.     if (ctx.methodString !== 'campaign.find' || (!ctx.args || !ctx.args.filter || !ctx.args.filter['count'])) {
  313.       return next();
  314.     } else {
  315.       Campaign.count(ctx.args.filter.where)
  316.         .then((count) => {
  317.           ctx.res.set('Access-Control-Expose-Headers', 'x-total-count');
  318.           ctx.res.set('X-Total-Count', count);
  319.           return next();
  320.         })
  321.         .catch((error) => {
  322.           return next();
  323.         });
  324.     }
  325.   });
  326.  
  327.   Campaign.getCountByStatus = (options, cb) => {
  328.     let authHeader = Utils.getRequestAuthHeaderSync(options);
  329.     let campaignCollection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
  330.     let advertiserId = authHeader.user.advertiserId;
  331.     campaignCollection.aggregate([
  332.       {
  333.         $match: { advertiserId: ObjectId(advertiserId) }
  334.       },
  335.       {
  336.         $group:
  337.           {
  338.             _id: '$_status.status',
  339.             num: { $sum: 1 }
  340.           }
  341.       },
  342.       {
  343.         $project:
  344.           {
  345.             '_id': 0,
  346.             'count': '$num',
  347.             'status': '$_id'
  348.           }
  349.       }
  350.     ], (err, data) => {
  351.       if (err) {
  352.         winston.info('Count campaigns by status error => ', err);
  353.         cb(err);
  354.       }
  355.       cb(null, data);
  356.     });
  357.   };
  358.  
  359.   Campaign.getStatisticsSummary = (options, cb) => {
  360.     let authHeader = Utils.getRequestAuthHeaderSync(options);
  361.     let campaignCollection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
  362.     let advertiserId = authHeader.user.advertiserId;
  363.     campaignCollection.aggregate([
  364.       {
  365.         $match: { advertiserId: ObjectId(advertiserId) }
  366.       },
  367.       {
  368.         $lookup:
  369.           {
  370.             from: 'campaignStatistics',
  371.             localField: '_id',
  372.             foreignField: 'campaignId',
  373.             as: 'campaignStatistics'
  374.           }
  375.       },
  376.       {
  377.         $addFields: {
  378.           campaignStatistics: {
  379.             $arrayElemAt: [{
  380.               $filter: {
  381.                 'input': '$campaignStatistics',
  382.                 'as': 'stat',
  383.                 'cond': { '$eq': ['$$stat.periodType', CampaignStatisticsPeriod.AllTime] }
  384.               }
  385.             }, 0]
  386.           }
  387.         },
  388.       },
  389.       {
  390.         $unwind: '$campaignStatistics'
  391.       },
  392.       {
  393.         $addFields:
  394.           {
  395.             'calcDays': { $add: [{ $floor: { $divide: [{ $subtract: [new Date(), '$startDate',] }, 60 * 60 * 24 * 1000] } }, 2] },
  396.             'allDays': { $add: [{ $floor: { $divide: [{ $subtract: ['$endDate', '$startDate',] }, 60 * 60 * 24 * 1000] } }, 2] }
  397.           }
  398.       },
  399.       {
  400.         $group:
  401.           {
  402.             _id: '$_status.status',
  403.             costs: { $sum: '$campaignStatistics.costs' },
  404.             impressions: { $sum: '$campaignStatistics.impressions' },
  405.             passedDays: { $sum: { $cond: [{ $gt: ['$calcDays', 0] }, { $cond: [{ $gt: ['$calcDays', '$allDays'] }, '$allDays', '$calcDays'] }, 0] } },
  406.             budget: { $sum: '$totalBudget' }
  407.           }
  408.       },
  409.       {
  410.         $project:
  411.           {
  412.             '_id': 0,
  413.             'status': '$_id',
  414.             'costs': { $divide: [{ $trunc: { $multiply: ['$costs', 100] } }, 100] },
  415.             'impressions': 1,
  416.             'avgCPI':
  417.               {
  418.                 $cond:
  419.                   {
  420.                     if: { $gt: ['$impressions', 0] },
  421.                     then: { $divide: [{ $trunc: { $multiply: [{ $divide: ['$costs', '$impressions'] }, 100] } }, 100] },
  422.                     else: 0
  423.                   }
  424.               },
  425.             'passedDays': 1,
  426.             'budget': 1
  427.           }
  428.       }
  429.     ], (err, data) => {
  430.       if (err) {
  431.         winston.info('Count campaigns by status error => ', err);
  432.         cb(err);
  433.       }
  434.       cb(null, data);
  435.     });
  436.   };
  437.  
  438.   Campaign.getImpressionsSummary = (id: any, filter: any, options: any, cb: any) => {
  439.     let authHeader = Utils.getRequestAuthHeaderSync(options);
  440.     let bpCardCollection = app.models.boardingCard.getDataSource().connector.collection(app.models.boardingCard.modelName);
  441.  
  442.     let queryDef = ImpressionsSummaryQuery(id, filter);
  443.  
  444.     let advertiserId = authHeader.user.advertiserId;
  445.  
  446.     waterfall([
  447.       (next) => {
  448.  
  449.         Campaign.findById(id, { fields: { advertiserId: true } }, (error, campaign) => {
  450.           if (!campaign || campaign.advertiserId.toString() !== advertiserId.toString()) {
  451.             let e: { status?: number } = new Error('Can\'t find campaign for advertiser');
  452.             e.status = 404;
  453.             next(e);
  454.  
  455.             return;
  456.           }
  457.  
  458.           next(error);
  459.         });
  460.       },
  461.       (next) => {
  462.         bpCardCollection.aggregate(queryDef.queryCount, (error, count) => {
  463.           next(error, (count && count.length) ? count[0].total : 0);
  464.         });
  465.       },
  466.       (count, next) => {
  467.         if (count === 0) {
  468.           cb(null, null, count);
  469.           return;
  470.         }
  471.  
  472.         bpCardCollection.aggregate(queryDef.query, (error, impressions) => {
  473.           cb(error, impressions, count);
  474.         });
  475.       }
  476.     ]);
  477.   };
  478.  
  479.   Campaign.updateStatistics = (campaign, boardingCard, amount, timestamp) => {
  480.     Promise.join(
  481.       findAndUpdateStatistic(campaign, boardingCard, amount, CampaignStatisticsPeriod.AllTime, timestamp),
  482.       findAndUpdateStatistic(campaign, boardingCard, amount, CampaignStatisticsPeriod.Monthly, timestamp),
  483.       findAndUpdateStatistic(campaign, boardingCard, amount, CampaignStatisticsPeriod.Daily, timestamp),
  484.     );
  485.   };
  486.  
  487.   const findAndUpdateStatistic = (campaign, boardingCard, amount, periodType, timestamp): Promise<CampaignStatistics> => {
  488.  
  489.     let condition: any = {
  490.       where: {
  491.         campaignId: campaign.id,
  492.         periodType: periodType
  493.       }
  494.     };
  495.  
  496.     if (periodType !== CampaignStatisticsPeriod.AllTime) {
  497.       let period = createPeriodDates(periodType, timestamp);
  498.       condition = {
  499.         where: {
  500.           campaignId: campaign.id,
  501.           periodType: periodType,
  502.           periodFrom: period.periodFrom,
  503.           periodTo: period.periodTo
  504.         }
  505.       };
  506.     }
  507.  
  508.     return app.models.campaignStatistics.findOne(condition)
  509.       .then(x => {
  510.         if (!x) {
  511.           return createStatistics(campaign, periodType, timestamp);
  512.         }
  513.  
  514.         return x;
  515.       })
  516.       .then(entity => {
  517.         return updateCampaignStatistics(entity, boardingCard, amount, campaign);
  518.       });
  519.   };
  520.  
  521.   const updateCampaignStatistics = (stat, boardingCard, amount, campaign) => {
  522.     let newState: any = {
  523.       advertiserId: campaign.advertiserId,
  524.  
  525.       periodType: stat.periodType,
  526.       periodFrom: stat.periodFrom,
  527.       periodTo: stat.periodTo,
  528.  
  529.       costs: _.round(stat.costs + amount, 2),
  530.       impressions: stat.impressions + 1,
  531.       avgCPI: 0,
  532.       routeStatistics: stat.routeStatistics
  533.     };
  534.  
  535.     newState.avgCPI = _.round(newState['costs'] / newState['impressions'], 2);
  536.  
  537.     let indx = _.findIndex(stat.routeStatistics, { route: boardingCard.orig + '-' + boardingCard.dest });
  538.     if (indx === -1) {
  539.       ////newState['routeStatistics'] = newState['routeStatistics'].concat(statistics['routeStatistics']);
  540.       newState.routeStatistics.push({
  541.         route: boardingCard.orig + '-' + boardingCard.dest,
  542.         costs: _.round(amount, 2),
  543.         impressions: 1,
  544.         avgCPI: _.round(amount, 2)
  545.       });
  546.     } else {
  547.       let newRouteState: any = {};
  548.       newRouteState.route = boardingCard.orig + '-' + boardingCard.dest;
  549.       newRouteState.costs = _.round(stat.routeStatistics[indx].costs + amount, 2);
  550.       newRouteState.impressions = stat.routeStatistics[indx].impressions + 1;
  551.       newRouteState.avgCPI = _.round(newRouteState.costs / newRouteState.impressions, 2);
  552.  
  553.       stat.routeStatistics.splice(indx, 1, newRouteState);
  554.       // stat.routeStatistics.push(newRouteState);
  555.       newState.routeStatistics = stat.routeStatistics;
  556.     }
  557.  
  558.     return stat.updateAttributes(newState);
  559.   };
  560.  
  561.   const createStatistics = (campaign, periodType, date?): Promise => {
  562.     let period = createPeriodDates(periodType, date);
  563.     let stat = {
  564.       periodType: periodType,
  565.       periodFrom: period.periodFrom,
  566.       periodTo: period.periodTo,
  567.       campaignId: campaign.id,
  568.       advertiserId: campaign.advertiserId,
  569.       avgCPI: 0,
  570.       costs: 0,
  571.       impressions: 0,
  572.       routeStatistics: []
  573.     };
  574.  
  575.     return app.models.campaignStatistics.create(stat);
  576.   };
  577.  
  578.   const createPeriodDates = (periodType, date?): any => {
  579.     let result = {
  580.       periodFrom: null,
  581.       periodTo: null
  582.     };
  583.  
  584.     if (date === undefined || date === null) {
  585.       date = new Date();
  586.     }
  587.  
  588.     if (periodType === CampaignStatisticsPeriod.Monthly) {
  589.       result.periodFrom = moment(date).startOf('month').toDate();
  590.       result.periodTo = moment(date).endOf('month').toDate();
  591.     }
  592.  
  593.     if (periodType === CampaignStatisticsPeriod.Daily) {
  594.       result.periodFrom = moment(date).startOf('day').toDate();
  595.       result.periodTo = moment(date).endOf('day').toDate();
  596.     }
  597.  
  598.     return result;
  599.   };
  600.  
  601.   Campaign.getCurrentStatus = (campaign, options) => {
  602.     let bpad = app.models.bpad;
  603.     let authHeader = Utils.getRequestAuthHeaderSync(options);
  604.     let data = {
  605.       user: authHeader.user,
  606.       roles: authHeader.roles,
  607.       isadmin: authHeader.isAdminUser,
  608.       campaign: campaign,
  609.       ad: null
  610.     };
  611.     return bpad.findById(campaign.ad.id).then(ad => {
  612.       data.ad = ad;
  613.       return data;
  614.     })
  615.       .then((x) => {
  616.         let stateMachine = Campaign.workflowStateMachineDefinition();
  617.         return stateMachine.initialize(x.campaign._status.status, x.campaign, x);
  618.       }).
  619.       then(machine => {
  620.         return machine.entity._status.status;
  621.       });
  622.   };
  623.  
  624.   Campaign.remoteMethod('getCurrentStatus', {
  625.     description: 'Returns campaigns current status. Includes validation on flight period.',
  626.     accepts: [
  627.       { arg: 'campaign', type: 'object', required: true },
  628.       { arg: 'options', 'type': 'object', 'http': 'optionsFromRequest' }
  629.     ],
  630.     returns: { arg: 'status', type: 'string' },
  631.     http: { verb: 'put', path: '/getCurrentStatus' }
  632.   });
  633.  
  634.   Campaign.getDays = (campaign) => {
  635.     let campaignStartDate = moment(campaign.startDate);
  636.     let campaignEndDate = moment(campaign.endDate);
  637.     let today = moment();
  638.     let passedDays = 0;
  639.  
  640.     let allDays = campaignEndDate.diff(campaignStartDate, 'days') + 1;
  641.  
  642.     if (today >= campaignStartDate.startOf('day')) {
  643.       if (today <= moment(campaignEndDate).endOf('day')) {
  644.         passedDays = today.diff(campaignStartDate, 'days') + 1;
  645.       } else {
  646.         passedDays = allDays;
  647.       }
  648.     }
  649.  
  650.     if (passedDays > allDays) {
  651.       passedDays = allDays;
  652.     }
  653.  
  654.     return {
  655.       allDays: allDays,
  656.       passedDays: passedDays
  657.     };
  658.   };
  659.  
  660.   Campaign.reach = (campaign: any, cb: any): void => {
  661.     //if campaign is standard
  662.     if (!campaign.isFixedInventory) {
  663.       getReachForStandardCampaign(campaign, (error, result) => {
  664.         if (error) {
  665.           cb(error);
  666.         }
  667.         cb(null, result);
  668.       });
  669.     }
  670.     else {
  671.       //else if campaign is premium
  672.       getReachForPremiumCampaign(campaign, (error, result) => {
  673.         if (error) {
  674.           cb(error);
  675.         }
  676.         cb(null, result);
  677.       });
  678.     }
  679.   };
  680.  
  681.   const getReachForPremiumCampaign = (campaign: any, cb): any => {
  682.     let flight = app.models.flight;
  683.     let flightsCount = 0;
  684.     let impressionsCount = 0;
  685.     const passengerCapacity = 189;
  686.     const loadFactor = 0.94;
  687.     let reach = {
  688.       flights: 0,
  689.       impressions: 0
  690.     };
  691.     async.forEach(campaign.routes, (route, callback) => {
  692.       let impressions = 0;
  693.       flight.count({
  694.         'and': [
  695.           {
  696.             'routeId': route.routeFrom + '-' + route.routeTo
  697.           },
  698.           {
  699.             'departureDate': { gte: (moment(campaign.startDate).format('YYYY-MM-DD') + 'T00:00:00.000Z') }
  700.           },
  701.           {
  702.             'departureDate': { lte: (moment(campaign.endDate).format('YYYY-MM-DD') + 'T00:00:00.000Z') }
  703.           }
  704.         ]
  705.       },
  706.         (error, count) => {
  707.           if (error) {
  708.             callback(error);
  709.           }
  710.           flightsCount += count;
  711.           impressions = Math.round(count * passengerCapacity * route.inventory * loadFactor);
  712.           impressionsCount += impressions;
  713.           callback();
  714.         });
  715.     }, (err) => {
  716.       if (err) {
  717.         cb(err);
  718.       }
  719.       reach.flights = flightsCount;
  720.       reach.impressions = Math.round(impressionsCount);
  721.       cb(null, reach);
  722.     });
  723.   };
  724.  
  725.   const getReachForStandardCampaign = (campaign: any, cb): any => {
  726.     let flight = app.models.flight;
  727.     let allFlights = 0;
  728.     let allImpressions = 0;
  729.     const passengerCapacity = 189;
  730.     const loadFactor = 0.94;
  731.     let reach = {
  732.       flights: 0,
  733.       impressions: 0
  734.     };
  735.     //find all campaigns with fixed inventory
  736.     Campaign.find(
  737.       {
  738.         where: {
  739.           and: [
  740.             { 'isFixedInventory': true },
  741.             { '_status.status': { include: [CampaignStatus.Running, CampaignStatus.Stopped, CampaignStatus.Approved] } },
  742.             ///{ or: [{ '_status.status': CampaignStatus.Approved }, { '_status.status': CampaignStatus.Running }, { '_status.status': CampaignStatus.Stopped }] },
  743.             { 'startDate': { lte: moment.utc(campaign.startDate).format() } },
  744.             { 'endDate': { gte: moment.utc(campaign.endDate).format() } }
  745.           ]
  746.         },
  747.         fields: { 'routes': true }
  748.       },
  749.       (error, fixedInventoryCampaigns) => {
  750.         //go through all selected routes in new campaign
  751.         async.forEach(campaign.routes,
  752.           (route, callback) => {
  753.             let flights = 0;
  754.             let impressions = 0;
  755.             let inventory = 0;
  756.             flight.count({
  757.               'and': [
  758.                 {
  759.                   'routeId': route.routeFrom + '-' + route.routeTo
  760.                 },
  761.                 {
  762.                   'departureDate': { gte: (moment(campaign.startDate).format('YYYY-MM-DD') + 'T00:00:00.000Z') }
  763.                 },
  764.                 {
  765.                   'departureDate': { lte: (moment(campaign.endDate).format('YYYY-MM-DD') + 'T00:00:00.000Z') }
  766.                 }
  767.               ]
  768.             },
  769.               (err, count) => {
  770.                 flights += count;
  771.                 //go through all premium campaigns to check if the selected route from new campaign is reserved
  772.                 fixedInventoryCampaigns.forEach(campaignObj => {
  773.                   //check all fixed inventory routes from one premium campaign
  774.                   campaignObj.routes.forEach(reservedRoute => {
  775.                     if (route.routeFrom === reservedRoute.routeFrom && route.routeTo === reservedRoute.routeTo) {
  776.                       inventory += reservedRoute.inventory;
  777.                     }
  778.                   });
  779.  
  780.                 });
  781.                 if (inventory > 1) {
  782.                   inventory = 1; //max inventory can be 100%
  783.                 }
  784.                 //increase capacity by fixed inventory for this route
  785.                 impressions = flights * (passengerCapacity * (1 - inventory));
  786.                 allFlights += flights;
  787.                 allImpressions += impressions;
  788.                 callback(); //go to next selected route in new campaign
  789.  
  790.               });
  791.  
  792.           },
  793.           (err) => {
  794.             if (err) {
  795.               cb(err);
  796.             }
  797.             reach.flights = allFlights;
  798.             reach.impressions = Math.round(allImpressions * loadFactor);
  799.             cb(null, reach);
  800.           });
  801.       });
  802.   };
  803.  
  804.   Campaign.won = (id: string, boardingCard: any, amount: number, timestamp: any, cb) => {
  805.     let boardingCardModel = app.models.boardingCard;
  806.  
  807.     waterfall([
  808.       (next) => {
  809.         winston.info('Wallet Charge find wallet started.');
  810.  
  811.         Campaign.findOne({ where: { id: id }, fields: { routes: false } }, (error, winningCampaign) => {
  812.           Campaign.updateStatistics(winningCampaign, boardingCard, amount, timestamp);
  813.           next(error, winningCampaign);
  814.         });
  815.       },
  816.       (winningCampaign, next) => {
  817.         winston.info('Wallet Charge create transaction started.');
  818.  
  819.         boardingCardModel.create({
  820.           request: boardingCard,
  821.           amount: amount,
  822.           timestamp: timestamp,
  823.           campaign: winningCampaign,
  824.           campaignId: winningCampaign.id,
  825.           advertiserId: winningCampaign.advertiserId
  826.         },
  827.           (error, transaction) => {
  828.             next(error, winningCampaign, amount);
  829.           });
  830.       },
  831.       (winningCampaign, winningAmount, next) => {
  832.         winston.info('Wallet Charge update wallet started.');
  833.         app.models.wallet.charge(winningCampaign, winningAmount, function (error: Error, results: any): void {
  834.           next(error, 'completed');
  835.         });
  836.       }
  837.     ],
  838.       (error, result) => {
  839.         if (error) {
  840.           winston.error(`Won bid error`);
  841.  
  842.           cb(error);
  843.         } else {
  844.           winston.info('Won bid completed.');
  845.  
  846.           cb(null, result);
  847.         }
  848.       });
  849.   };
  850.  
  851.   Campaign.observe('before save', (ctx, next) => {
  852.  
  853.     let valid = true;
  854.     if (ctx.instance) {
  855.       // validation on route inventory
  856.       if (ctx.instance.isFixedInventory) {
  857.         ctx.instance.routes.forEach(route => {
  858.           let inventory = _.round(route.inventory * 100, 0);
  859.           if (inventory < 1 || inventory > 100 || !Number.isInteger(inventory)) {
  860.             let error = new Error('Validation error: Inventory should be a whole number and in range [1-100].');
  861.             valid = false;
  862.             return next(error);
  863.           }
  864.         });
  865.       }
  866.     }
  867.  
  868.     if (valid) {
  869.       updateBudgetAndImpressionsLimits(ctx.instance).then(() => {
  870.         if (ctx.isNewInstance) {
  871.           if (!ctx.instance._status.status) {
  872.             ctx.instance._status.status = CampaignStatus.Draft;
  873.             ctx.instance._status.lastChangeDate = moment.utc();
  874.           }
  875.  
  876.           return next();
  877.         }
  878.  
  879.         if (!ctx.instance) {
  880.           // not full update
  881.           return next();
  882.         }
  883.  
  884.         Campaign.findById(ctx.instance.id, { fields: { _status: true, statusLog: true } })
  885.           .then((campaign) => {
  886.             // preserve those fields
  887.             ctx.instance._status = campaign._status;
  888.  
  889.             if (!ctx.instance.statusLog) {
  890.               ctx.instance.statusLog = campaign.statusLog;
  891.             }
  892.  
  893.             return next();
  894.           }, (error) => {
  895.             return next(error);
  896.           });
  897.       });
  898.     }
  899.   });
  900.  
  901.   const updateBudgetAndImpressionsLimits = (campaign: any): Promise => {
  902.  
  903.     return new Promise((resolve, reject) => {
  904.       if (campaign && campaign.isFixedInventory) {
  905.         let impressionsLimit = 0;
  906.         getReachForPremiumCampaign(campaign, (err, reach) => {
  907.           if (err) {
  908.             return reject(new Error(err));
  909.           }
  910.           campaign.impressionsLimit = reach.impressions;
  911.           campaign.totalBudget = reach.impressions * campaign.maxCPI;
  912.           return resolve();
  913.         });
  914.       } else {
  915.         return resolve();
  916.       }
  917.     });
  918.   };
  919.  
  920.   function getRoutes(id: any, routes: any): Promise<any> {
  921.     return new Promise((resolve, reject) => {
  922.       if (!routes && id) {
  923.         Campaign.findById(id, { fields: { routes: true } }, (error, results) => {
  924.           if (error) {
  925.             winston.error('Error occured while searching campaign routes.');
  926.             reject(error);
  927.           } else if (results.routes) {
  928.             let routeIds = _.map(results.routes, 'routeId');
  929.             resolve(routeIds);
  930.           } else {
  931.             winston.info('No matching routes find.');
  932.             let e: { status?: number } = new Error('No matching routes find');
  933.             e.status = 404;
  934.             reject(e);
  935.           }
  936.         });
  937.       } else {
  938.         resolve(routes);
  939.       }
  940.     });
  941.   }
  942.  
  943.   function getAggregationMatchingPublishers(routes: any): Promise<any> {
  944.     return new Promise((resolve, reject) => {
  945.       let publisherRouteCollection = app.models.publisherRoute.getDataSource().connector.collection(app.models.publisherRoute.modelName);
  946.       publisherRouteCollection.aggregate([
  947.         {
  948.           $match: { routeId: { $in: routes } }
  949.         },
  950.         {
  951.           $lookup: {
  952.             from: 'publisher',
  953.             localField: 'publisherId',
  954.             foreignField: '_id',
  955.             as: 'publisher'
  956.           }
  957.         },
  958.         {
  959.           $lookup: {
  960.             from: 'route',
  961.             localField: 'routeId',
  962.             foreignField: '_id',
  963.             as: 'route'
  964.           }
  965.         },
  966.         {
  967.           $unwind: '$publisher'
  968.         },
  969.         {
  970.           $unwind: '$route'
  971.         },
  972.         {
  973.           $facet:
  974.             {
  975.               routesPublishers: [
  976.                 {
  977.                   $group: {
  978.                     _id: '$routeId',
  979.                     origCode: { $first: '$route.origCode' },
  980.                     destCode: { $first: '$route.destCode' },
  981.                     publishers: {
  982.                       $push: {
  983.                         'routeId': '$routeId',
  984.                         '_id': '$publisher._id',
  985.                         'name': '$publisher.name'
  986.                       }
  987.                     }
  988.                   }
  989.                 }
  990.               ],
  991.               distinctPublishers: [
  992.                 {
  993.                   $group: {
  994.                     _id: '$publisherId',
  995.                     publisher: { $first: '$publisher' }
  996.                   }
  997.                 },
  998.                 {
  999.                   $project: {
  1000.                     '_id': '$publisher._id',
  1001.                     'name': '$publisher.name'
  1002.                   }
  1003.                 }
  1004.               ]
  1005.             }
  1006.         }
  1007.       ], (err, data) => {
  1008.         if (err) {
  1009.           reject(err);
  1010.         } else {
  1011.           resolve(data);
  1012.         }
  1013.       });
  1014.     });
  1015.   }
  1016.  
  1017.   function getAggregationBlockedPublishers(advertiserId: any): Promise<any> {
  1018.     return new Promise((resolve, reject) => {
  1019.       let advertiserPublisherCollection = app.models.advertiserPublisher.getDataSource().connector.collection(app.models.advertiserPublisher.modelName);
  1020.       advertiserPublisherCollection.aggregate([
  1021.         {
  1022.           $match: {
  1023.             advertiserId: ObjectId(advertiserId),
  1024.             '_status.status': { $eq: 'blocked' }
  1025.           }
  1026.         },
  1027.         {
  1028.           $project: {
  1029.             '_id': '$publisherId'
  1030.           }
  1031.         }
  1032.       ], (err, data) => {
  1033.         if (err) {
  1034.           reject(err);
  1035.         } else {
  1036.           resolve(data);
  1037.         }
  1038.       });
  1039.     });
  1040.   }
  1041.  
  1042.   Campaign.matchingPublishers = (options: any, routes: any, campaignId: string, cb: any) => {
  1043.  
  1044.     async.waterfall([
  1045.       (callback) => {
  1046.         getRoutes(campaignId, routes)
  1047.           .then(checkRoutes => {
  1048.             callback(null, checkRoutes);
  1049.             return null;
  1050.           })
  1051.           .catch((err) => {
  1052.             winston.info('Error from >>getRoutes.');
  1053.             cb(err);
  1054.           });
  1055.       },
  1056.       (checkRoutes) => {
  1057.         async.parallel({
  1058.           matching: (callback) => {
  1059.             getAggregationMatchingPublishers(checkRoutes).then(response => {
  1060.               callback(null, response);
  1061.             })
  1062.               .catch((err) => {
  1063.                 winston.info('Error from >>getRoutes.');
  1064.                 cb(err);
  1065.               });
  1066.           },
  1067.           blocked: (callback) => {
  1068.             let authHeader = Utils.getRequestAuthHeaderSync(options);
  1069.             getAggregationBlockedPublishers(authHeader.user.advertiserId).then(response => {
  1070.               callback(null, response);
  1071.             })
  1072.               .catch((err) => {
  1073.                 winston.info('Error from >>getRoutes.');
  1074.                 cb(err);
  1075.               });
  1076.           }
  1077.         }, (err, results) => {
  1078.           if (err) {
  1079.             cb(err);
  1080.           } else {
  1081.             // results.matching[0].routesPublishers = _.reduce(results.matching[0].routesPublishers, function (obj: any, param: any): any {
  1082.             //   obj[param._id] = param.publishers;
  1083.             //   return obj;
  1084.             // }, {});
  1085.             results.blocked = _.map(results.blocked, '_id');
  1086.             cb(null, results);
  1087.           }
  1088.         });
  1089.       }
  1090.     ]);
  1091.  
  1092.     /*
  1093.       //Aggregation for getting distint not blocked publishers
  1094.       [
  1095.         { //Match routes
  1096.             $match: { routeId: { $in: ["STN-DUB","STN-BRI", "EMA-WMI"] } }
  1097.         },
  1098.         { //Get publishers of routes
  1099.         $lookup: {
  1100.               from: 'publisher',
  1101.               localField: 'publisherId',
  1102.               foreignField: '_id',
  1103.               as: 'publisher'
  1104.           }
  1105.         },
  1106.         { //Move publisher to upper level
  1107.           $unwind: '$publisher'
  1108.         },
  1109.         { //Add advertiserId variable while projecting result
  1110.             $project: {
  1111.                 _id: 1,
  1112.                 routeId: 1,
  1113.                 publisher: 1,
  1114.                 publisherId: 1,
  1115.                 advertiserId: ObjectId('5a4f6acd8368b434dc4f633a')
  1116.                 }
  1117.         },
  1118.         //Find advertiserPublishers
  1119.         //In version 3.6+ we could use pipeline in lookup
  1120.         //https://stackoverflow.com/questions/37086387/multiple-join-conditions-using-the-lookup-operator
  1121.         {
  1122.             $lookup: {
  1123.               from: 'advertiserPublisher',
  1124.               localField: 'advertiserId',
  1125.               foreignField: 'advertiserId',
  1126.               as: 'advertiserPublisher'
  1127.           }
  1128.         },
  1129.         { //We receive multiple advertiser publisher result, we expand list for next matching with publisherId
  1130.           $unwind: '$advertiserPublisher'
  1131.         },
  1132.         { //We create another projection with cond
  1133.             $project: {
  1134.                 _id: 1,
  1135.                 publisher: 1,
  1136.                 routeId: 1,
  1137.                 publisherId: 1,
  1138.                 advertiserId: 1,
  1139.                 advertiserPublisher: 1,
  1140.                 isBlocked: {
  1141.                       $and: [
  1142.                                 {$eq: ['$advertiserPublisher.publisherId', '$publisherId']},
  1143.                                 {$eq: ['$advertiserPublisher._status.status', 'blocked']}
  1144.                       ]
  1145.                 },
  1146.                 publisherIdMatch: {
  1147.                       $and: [
  1148.                                 {$eq: ['$advertiserPublisher.publisherId', '$publisherId']}
  1149.                       ]
  1150.                 }
  1151.             }
  1152.         },
  1153.         { //Return only matcing records
  1154.             $match: {
  1155.                 'isBlocked': false,
  1156.                 'publisherIdMatch': true
  1157.             }
  1158.         },
  1159.         {
  1160.             $group: {
  1161.                 _id: '$publisherId',
  1162.                 publisher: { $first: '$publisher' }
  1163.             }
  1164.         },
  1165.         {
  1166.             $project: {
  1167.                 '_id': '$publisher._id',
  1168.                 'name': '$publisher.name'
  1169.               }
  1170.         }
  1171.     ]
  1172.     */
  1173.  
  1174.   };
  1175.  
  1176.  function getAggregationCampaignAds(campaignId: any): Promise<any> {
  1177.    return new Promise((resolve, reject) => {
  1178.      let campaignCollection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
  1179.      campaignCollection.aggregate([
  1180.        //Match campaignId
  1181.        {
  1182.          $match: { _id: ObjectId(campaignId) }
  1183.        },
  1184.        //Get all campaign publishers
  1185.        {
  1186.          $lookup: {
  1187.            from: 'campaignPublisher',
  1188.            localField: '_id',
  1189.            foreignField: 'campaignId',
  1190.            as: 'campaignPublisher'
  1191.          }
  1192.        },
  1193.        //Unwind campaign publishers
  1194.        {
  1195.          $unwind: '$campaignPublisher'
  1196.        },
  1197.        //Find ads of certain publisher
  1198.        //In version 3.6+ we could use pipeline in lookup
  1199.        //https://stackoverflow.com/questions/37086387/multiple-join-conditions-using-the-lookup-operator
  1200.        {
  1201.          $lookup: {
  1202.            from: 'adStatus',
  1203.            localField: 'bpadId',
  1204.            foreignField: 'bpadId',
  1205.            as: 'mainAd'
  1206.          }
  1207.        },
  1208.        {
  1209.          $unwind: '$mainAd'
  1210.        },
  1211.        {
  1212.          $unwind: '$mainAd._content'
  1213.        },
  1214.        {
  1215.          $project: {
  1216.            document: '$$ROOT',
  1217.            publisherIdMatch: {
  1218.              $and: [
  1219.                { $eq: ['$mainAd.publisherId', '$campaignPublisher.publisherId'] }
  1220.              ]
  1221.            }
  1222.          }
  1223.        },
  1224.        {
  1225.          $match: { 'publisherIdMatch': true }
  1226.        },
  1227.        {
  1228.          $group: {
  1229.            _id: '$$ROOT.document._id',
  1230.            campaign: { $first: '$$ROOT.document' },
  1231.            images: {
  1232.              //$push: '$$ROOT.document.mainAd'
  1233.              $push: {
  1234.                'imageUrl': '$$ROOT.document.mainAd._content.imageUrl',
  1235.                'language': '$$ROOT.document.mainAd._content.language',
  1236.                'publisherId': '$$ROOT.document.mainAd.publisherId'
  1237.              }
  1238.            }
  1239.          }
  1240.        },
  1241.        {
  1242.          $project: {
  1243.            id: '$campaign._id',
  1244.            //title: '$campaign.title',
  1245.            //description: '$campaign.description',
  1246.            //targetEarly: '$campaign.targetEarly',
  1247.            //targetLate: '$campaign.targetLate',
  1248.            //statusLog: '$campaign.statusLog',
  1249.            //_status: '$campaign._status',
  1250.            startDate: '$campaign.startDate',
  1251.            endDate: '$campaign.endDate',
  1252.            maxCPI: '$campaign.maxCPI',
  1253.            totalBudget: '$campaign.totalBudget',
  1254.            impressionsLimit: '$campaign.impressionsLimit',
  1255.            maxDailyBudget: '$campaign.maxDailyBudget',
  1256.            baggageType: '$campaign.baggageType',
  1257.            fareClass: '$campaign.fareClass',
  1258.            ageFrom: '$campaign.ageFrom',
  1259.            ageTo: '$campaign.ageTo',
  1260.            gender: '$campaign.gender',
  1261.            routes: '$campaign.routes',
  1262.            dateCreated: '$campaign.dateCreated',
  1263.            dateModified: '$campaign.dateModified',
  1264.            isFixedInventory: '$campaign.isFixedInventory',
  1265.            advertiserId: '$campaign.advertiserId',
  1266.            channels: {
  1267.              pull: {
  1268.                image: '$images'
  1269.              }
  1270.            }
  1271.          }
  1272.        }
  1273.      ], (err, data) => {
  1274.        if (err) {
  1275.          reject(err);
  1276.        } else {
  1277.          resolve(data[0]);
  1278.        }
  1279.      });
  1280.    });
  1281.  }
  1282.  
  1283.  
  1284.   Campaign.observe('after save', (ctx, next) => {
  1285.  
  1286.     // getAggregationCampaignAds(ctx.instance.id).then(response => {
  1287.     //   winston.info('CAMPAIGN DATA', JSON.stringify(response, null, 2));
  1288.     // })
  1289.     //   .catch((err) => {
  1290.     //     winston.info('Error while retrieving campaign information');
  1291.     //     return next(err);
  1292.     //   });
  1293.     let campaignData;
  1294.  
  1295.     if (ctx.isNewInstance) {
  1296.       Promise.join(
  1297.  
  1298.         createStatistics(ctx.instance, CampaignStatisticsPeriod.AllTime),
  1299.  
  1300.         /////ctx.instance.campaignStatistics.create({ avgCPI: 0, costs: 0, impressions: 0, routeStatistics: [] }),
  1301.         ////NOTE: update originFrom on bpad....
  1302.         app.models.bpad.updateAll({
  1303.           and: [
  1304.             { id: ObjectId(ctx.instance.ad ? ctx.instance.ad.id : null) },
  1305.             { '_status.status': AdStatus.Draft }
  1306.           ]
  1307.         },
  1308.           {
  1309.             originFrom: ctx.instance.id
  1310.           }),
  1311.         app.models.bpad.findById(ctx.instance.ad.id),
  1312.         // app.models.bpad.find({ where: {
  1313.         //   campaigns: { inq:  ctx.instance.id }
  1314.         // }})
  1315.       )
  1316.         .spread((campaignStatistics, ad, newAd) => {
  1317.           return adAppendCampaignId(newAd, ctx.instance.id);
  1318.         })
  1319.         .then(() => {
  1320.           return next();
  1321.         })
  1322.         .catch((error) => {
  1323.           winston.error(`Ad status can't be changed. Error stack trace: ${error}`);
  1324.           return next(error);
  1325.         });
  1326.     }
  1327.     else if (ctx.data) {
  1328.       next();
  1329.     }
  1330.     else {
  1331.       ///let campaignIdString = ctx.instance.id.toString();
  1332.       Promise.join(
  1333.         app.models.bpad.find({
  1334.           where: {
  1335.             advertiserId: ctx.instance.advertiserId,
  1336.             campaigns: ctx.instance.id,
  1337.             id: { neq: ctx.instance.ad.id }
  1338.           }
  1339.         }),
  1340.         app.models.bpad.findById(ctx.instance.ad.id),
  1341.         getAggregationCampaignAds(ctx.instance.id)
  1342.       )
  1343.         .spread((otherAds, ad, campaignDataAggregationResults) => {
  1344.           campaignData = campaignDataAggregationResults;
  1345.           ////let adId = ad.id.toString();
  1346.           let promiseArray = [];
  1347.  
  1348.           promiseArray.push(adAppendCampaignId(ad, ctx.instance.id));
  1349.           otherAds.forEach(element => {
  1350.             promiseArray.push(adRemoveCampaignId(element, ctx.instance.id));
  1351.           });
  1352.  
  1353.           return Promise.join(
  1354.             promiseArray
  1355.             // adRemoveCampaignId(oldAd, ctx.instance.id),
  1356.             // adAppendCampaignId(ad, ctx.instance.id)
  1357.           );
  1358.         })
  1359.         .then(() => {
  1360.           return app.models.bpad.updateAll({ eq: ['originFrom', ctx.instance.id] }, { originFrom: null })
  1361.             .then((updatedAll) => {
  1362.               return app.models.bpad.updateAll(
  1363.                 { id: ctx.instance.ad.id, '_status.status': AdStatus.Draft },
  1364.                 { originFrom: ctx.instance.id, '_status.status': AdStatus.Submitted }
  1365.               )
  1366.                 .then((updatedInstance) => {
  1367.                   let campaignLastChange = moment(ctx.instance.modified).milliseconds(0).toISOString();
  1368.  
  1369.                   let statusLastChange = '';
  1370.                   if (ctx.instance.statusLog && ctx.instance.statusLog.length > 0) {
  1371.                     let lastStatusLog = _.last(ctx.instance.statusLog);
  1372.                     statusLastChange = moment(lastStatusLog.lastChangeDate).milliseconds(0).toISOString();
  1373.                   }
  1374.  
  1375.                   if (campaignLastChange === statusLastChange && (ctx.instance._status.status === CampaignStatus.Running || ctx.instance._status.status === CampaignStatus.Stopped || ctx.instance._status.status === CampaignStatus.SystemStopped || ctx.instance._status.status === CampaignStatus.Ended || ctx.instance._status.status === CampaignStatus.Blocked)) {
  1376.                     winston.info('Campaign Status Changed send notification started.', ctx.instance._status.status);
  1377.                     return app.queue.CampaignStatusChanged.push({
  1378.                       campaign: campaignData,
  1379.                       status: campaignData._status.status
  1380.                     }).then(() => {
  1381.                       winston.info(`Campaign Status Changed send notification finished.`, campaignData._status.status);
  1382.                       return next();
  1383.                     });
  1384.  
  1385.                   } else if (CampaignStatus.Running === ctx.instance._status.status) {
  1386.                     winston.info('Campaign Changed send notification started for: ', ctx.instance.id);
  1387.  
  1388.                     return app.queue.CampaignModify.push({
  1389.                       campaign: campaignData
  1390.                     }).then(() => {
  1391.                       winston.info('Campaign Changed send notification finished for: ', ctx.instance.id);
  1392.                       return next();
  1393.                     });
  1394.                   } else {
  1395.                     return next();
  1396.                   }
  1397.                 });
  1398.             });
  1399.         })
  1400.         .catch((error) => {
  1401.           winston.error(`Ad status can't be changed. Error stack trace: ${error}`);
  1402.           return next(error);
  1403.         });
  1404.     }
  1405.   });
  1406.  
  1407.   function adAppendCampaignId(ad: any, campaignId: any): Promise {
  1408.     if (!ad) {
  1409.       return Promise.resolve();
  1410.     }
  1411.  
  1412.     let campaignIds = [];
  1413.     if (ad.campaigns) {
  1414.       campaignIds = ad.campaigns;
  1415.     }
  1416.  
  1417.     ////let campaignIdString = campaignId.toString();
  1418.     let indx = campaignIds.map(x => x.toString()).indexOf(campaignId.toString());
  1419.     /////_.findIndex(campaignIds, { id: campaignId.toString() });
  1420.     ////;
  1421.  
  1422.     if (indx < 0) {
  1423.       campaignIds.push(campaignId);
  1424.       let hasCampaign = campaignIds.length > 0;
  1425.       return ad.updateAttributes({ campaigns: campaignIds, used: hasCampaign });
  1426.     }
  1427.  
  1428.     return Promise.resolve();
  1429.   }
  1430.  
  1431.   function adRemoveCampaignId(ad: any, campaignId: any): Promise {
  1432.     if (!ad) {
  1433.       return Promise.resolve();
  1434.     }
  1435.  
  1436.     if (ad.campaigns) {
  1437.       ////let campaignIdString = campaignId.toString();
  1438.       let indx = ad.campaigns.map(x => x.toString()).indexOf(campaignId.toString()); ////ad.campaigns.indexOf(campaignId);
  1439.  
  1440.       ////_.findIndex(ad.campaigns, { id: campaignId.toString() });
  1441.       ////ad.campaigns.indexOf(campaignIdString);
  1442.       if (indx > -1) {
  1443.         ad.campaigns.splice(indx, 1);
  1444.         let hasCampaign = ad.campaigns.length > 0;
  1445.  
  1446.         return ad.updateAttributes({
  1447.           campaigns: ad.campaigns,
  1448.           used: hasCampaign
  1449.         });
  1450.       }
  1451.     }
  1452.  
  1453.     return Promise.resolve();
  1454.   }
  1455.  
  1456.   Campaign.actions = (campaignId, options, cb) => {
  1457.     let bpad = app.models.bpad;
  1458.  
  1459.     let data = prepareStateMachineData(options);
  1460.  
  1461.     initializeStateMachine(campaignId, data)
  1462.       .then(machine => {
  1463.         return machine.getActions(data);
  1464.       }).
  1465.       then(actions => {
  1466.         let actions_flat = [];
  1467.         if (actions !== undefined) {
  1468.           actions_flat = actions.map(element => element.name);
  1469.         }
  1470.  
  1471.         cb(null, actions_flat);
  1472.         return;
  1473.       })
  1474.       .catch((err) => {
  1475.         winston.info('Error from >>Get available actions.');
  1476.         cb(err);
  1477.         return;
  1478.       });
  1479.   };
  1480.  
  1481.   Campaign.remoteMethod('actions', {
  1482.     description: 'Get available actions for campaign.',
  1483.     accepts: [
  1484.       { arg: 'campaignId', type: 'string', required: true },
  1485.       { arg: 'options', 'type': 'object', 'http': 'optionsFromRequest' }
  1486.     ],
  1487.     returns: { arg: 'availableActions', type: 'array' },
  1488.     http: { verb: 'get', path: '/:campaignId/actions/' }
  1489.   });
  1490.  
  1491.   Campaign.executeAction = (campaignId: string, action: string, data: any, options: any) => {
  1492.     let bpad = app.models.bpad;
  1493.  
  1494.     // tslint:disable-next-line:no-console
  1495.     //console.log('__ bpad: ', bpad);
  1496.  
  1497.     let authHeader = Utils.getRequestAuthHeaderSync(options);
  1498.     let user = authHeader.user;
  1499.  
  1500.     let contextData = prepareStateMachineData(options);
  1501.     contextData.submitted = data;
  1502.     contextData.action = action;
  1503.  
  1504.     return initializeStateMachine(campaignId, contextData)
  1505.       .then(machine => {
  1506.         // tslint:disable-next-line:no-console
  1507.         //console.log('__ machine: ', machine);
  1508.  
  1509.         return machine.execute(action, contextData);
  1510.       })
  1511.       .then(x => {
  1512.         return x;
  1513.       })
  1514.       .catch((err) => {
  1515.         winston.error(`Error executing action ${action} on campaign ${campaignId}.`, { error: err, campaignId: campaignId, action: action, data: data, options: options });
  1516.  
  1517.         if (err && err.reason) {
  1518.           if (err.reason === WorkflowResults.ACTION_NOT_ALLOWED) {
  1519.             winston.info(`Action ${contextData.action} is not allowed.`);
  1520.             return Promise.reject(new BadRequest(BadRequest.CAMPAIGN_ACTION_NOT_ALLOWED));
  1521.           }
  1522.  
  1523.           return Promise.reject(new BadRequest(err.reason));
  1524.         }
  1525.  
  1526.         return Promise.reject(err);
  1527.       });
  1528.   };
  1529.  
  1530.   Campaign.remoteMethod('executeAction', {
  1531.     description: 'Executes actions on .',
  1532.     accepts: [
  1533.       { arg: 'campaignId', type: 'string', required: true },
  1534.       { arg: 'action', type: 'string', required: true },
  1535.       { arg: 'data', type: 'object', required: false },
  1536.       { arg: 'options', 'type': 'object', 'http': 'optionsFromRequest' }
  1537.     ],
  1538.     returns: { arg: 'campaign', type: 'object' },
  1539.     http: { verb: 'post', path: '/:campaignId/execute/:action/' }
  1540.   });
  1541.  
  1542.   const initializeStateMachine = (campaignId: string, contextData: any): Promise<StateMachine<any>> => {
  1543.     let bpad = app.models.bpad;
  1544.  
  1545.     return Campaign.findById(campaignId)
  1546.       .then((campaign) => {
  1547.         if (!campaign) {
  1548.           let error = new NotFound(NotFound.CAMPAIGN_NOT_FOUND);
  1549.           return Promise.reject(error);
  1550.         }
  1551.  
  1552.         if (contextData.action === 'approve') {
  1553.           app.models.adnexaUser.findOne({ where: { advertiserId: campaign.advertiserId } }, (error, advertiser) => {
  1554.             winston.info('Find advertiser finished.', advertiser);
  1555.             ejs.renderFile(path.resolve(__dirname, '../views/campaignApproved.ejs'),
  1556.               { fullName: advertiser.fullName, campaignTitle: campaign.title, campaignUrl: campaign.url, url: client.url + 'campaigns/' + campaign.id, img: client.url + 'assets/img/header.png' }, {}, (ejsError, html) => {
  1557.                 Utils.sendMail({
  1558.                   to: advertiser.email,
  1559.                   subject: 'Advert approved for your campaign',
  1560.                   html
  1561.                 }, (err: Error) => {
  1562.                   if (err) { return winston.info('> error sending campaign approved email'); }
  1563.                   winston.info('> sending campaign approved email to:', advertiser.email);
  1564.                 });
  1565.               });
  1566.           });
  1567.         }
  1568.  
  1569.         contextData.campaign = campaign;
  1570.         return initializeStateMachineByInstance(campaign, contextData);
  1571.       });
  1572.   };
  1573.  
  1574.   const initializeStateMachineByInstance = (campaign, contextData): Promise<StateMachine<any>> => {
  1575.     // let bpad = app.models.bpad;
  1576.     let adStatus = app.models.adStatus;
  1577.     let advertiser = app.models.advertiser;
  1578.  
  1579.     contextData.campaign = campaign;
  1580.  
  1581.     return adStatus.findById(campaign.ad.id).then((ad) => {
  1582.       contextData.ad = ad;
  1583.       return contextData;
  1584.     })
  1585.       .then((x) => {
  1586.         return advertiser.findById(campaign.advertiserId)
  1587.           .then(advr => {
  1588.             x.advertiser = advr;
  1589.             return x;
  1590.           });
  1591.       })
  1592.       .then((x) => {
  1593.         let stateMachine = Campaign.workflowStateMachineDefinition();
  1594.         return stateMachine.initialize(x.campaign._status.status, x.campaign, x);
  1595.       });
  1596.   };
  1597.  
  1598.   Campaign.changeStatus = (campaign, status, context): Promise<any> => {
  1599.     if (campaign._status.status === status) {
  1600.       return Promise.resolve(campaign);
  1601.     }
  1602.  
  1603.     let lastChangeDate = Date.now();
  1604.     let previous = campaign._status.status;
  1605.     let newStatus = {
  1606.       status: status,
  1607.       lastChangeDate: lastChangeDate
  1608.     };
  1609.     let statusLog = {
  1610.       status: status,
  1611.       previous: previous,
  1612.       lastChangeDate: lastChangeDate,
  1613.       userid: context.user.id
  1614.     };
  1615.  
  1616.     if (campaign.statusLog) {
  1617.       campaign.statusLog.push(statusLog);
  1618.     } else {
  1619.       campaign.statusLog = [statusLog];
  1620.     }
  1621.  
  1622.     return campaign.updateAttributes({
  1623.       _status: newStatus,
  1624.       statusLog: campaign.statusLog
  1625.     }).then(x => {
  1626.       return x;
  1627.     });
  1628.   };
  1629.  
  1630.   Campaign.workflowStateMachineDefinition = (): StateMachine<any> => {
  1631.  
  1632.     let blockAction = new StateAction<any>(
  1633.       {
  1634.         to: CampaignStatus.Blocked,
  1635.         name: CampaignActions.Block,
  1636.         conditions: [isAdminUser, isAdvertiserBlock]
  1637.       });
  1638.  
  1639.     let endAction = new StateAction<any>(
  1640.       {
  1641.         to: CampaignStatus.Ended,
  1642.         name: CampaignActions.End,
  1643.         auto: true,
  1644.         conditions: [
  1645.           advertiserNotBlocked,
  1646.           (entity, context) => { return Promise.resolve(entity.endDate < Date.now() && entity._status.status !== CampaignStatus.Ended); }],
  1647.       });
  1648.  
  1649.     return new StateMachine<any>(
  1650.       [
  1651.         new State<any>({
  1652.           name: CampaignStatus.Draft,
  1653.           actions: [
  1654.             endAction,
  1655.             blockAction,
  1656.             new StateAction<any>({
  1657.               name: CampaignActions.Edit,
  1658.               conditions: [advertiserNotBlocked, isAdvertiser]
  1659.             }),
  1660.             new StateAction<any>(
  1661.               {
  1662.                 to: CampaignStatus.Running,
  1663.                 name: CampaignActions.Run,
  1664.                 conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved, haveFunds]
  1665.               }),
  1666.             new StateAction<any>(
  1667.               {
  1668.                 to: CampaignStatus.Submitted,
  1669.                 name: CampaignActions.Submit,
  1670.                 action: submitCampaignAd,
  1671.                 conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, isQuickAd, adNotRejectedOrBlocked]
  1672.               }),
  1673.             new StateAction<any>(
  1674.               {
  1675.                 to: CampaignStatus.Running,
  1676.                 name: CampaignActions.Run,
  1677.                 conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved, haveFunds]
  1678.               }),
  1679.             new StateAction<any>(
  1680.               {
  1681.                 to: CampaignStatus.Submitted,
  1682.                 name: CampaignActions.Submit,
  1683.                 action: submitCampaignAd,
  1684.                 conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, isQuickAd]
  1685.               }),
  1686.             ////NOTE: premium advertiser submit action.
  1687.             new StateAction<any>(
  1688.               {
  1689.                 to: CampaignStatus.Submitted,
  1690.                 name: CampaignActions.Submit,
  1691.                 action: submitCampaignAd,
  1692.                 conditions: [advertiserNotBlocked, isPremiumAdvertiser, adNotRejectedOrBlocked]
  1693.               }),
  1694.           ]
  1695.         }),
  1696.         new State<any>({
  1697.           name: CampaignStatus.Submitted,
  1698.           actions: [
  1699.             endAction,
  1700.             blockAction,
  1701.             new StateAction<any>(
  1702.               {
  1703.                 to: CampaignStatus.Draft,
  1704.                 name: CampaignActions.EditRequested,
  1705.                 conditions: [advertiserNotBlocked, isAdvertiser]
  1706.               }),
  1707.             new StateAction<any>(
  1708.               {
  1709.                 to: CampaignStatus.Running,
  1710.                 name: CampaignActions.Run,
  1711.                 conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved, haveFunds]
  1712.               }),
  1713.             new StateAction<any>(
  1714.               {
  1715.                 to: CampaignStatus.Draft,
  1716.                 name: CampaignActions.EditRequested,
  1717.                 conditions: [advertiserNotBlocked, isAdvertiser]
  1718.               }),
  1719.             new StateAction<any>(
  1720.               {
  1721.                 to: CampaignStatus.Running,
  1722.                 name: CampaignActions.Run,
  1723.                 conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved]
  1724.               }),
  1725.             new StateAction<any>(
  1726.               {
  1727.                 to: CampaignStatus.Approved,
  1728.                 name: CampaignActions.Approve,
  1729.                 action: approveCampaignAd,
  1730.                 conditions: [advertiserNotBlocked, isAdminUser, isPremiumAdvertiser, adNotRejectedOrBlocked],
  1731.               }),
  1732.             ////NOTE: Quick AD approval...
  1733.             new StateAction<any>(
  1734.               {
  1735.                 to: CampaignStatus.Approved,
  1736.                 name: CampaignActions.Approve,
  1737.                 action: approveCampaignAd,
  1738.                 conditions: [advertiserNotBlocked, notPremiumAdvertiser, isAdminUser, adNotRejectedOrBlocked],
  1739.               }),
  1740.             new StateAction<any>(
  1741.               {
  1742.                 to: CampaignStatus.Rejected,
  1743.                 name: CampaignActions.Reject,
  1744.                 conditions: [advertiserNotBlocked, isAdminUser],
  1745.               }),
  1746.           ]
  1747.         }),
  1748.         new State<any>({
  1749.           name: CampaignStatus.Approved,
  1750.           actions: [
  1751.             endAction,
  1752.             blockAction,
  1753.             new StateAction<any>(
  1754.               {
  1755.                 to: CampaignStatus.Running,
  1756.                 name: CampaignActions.Run,
  1757.                 conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved, haveFunds]
  1758.               }),
  1759.             new StateAction<any>(
  1760.               {
  1761.                 to: CampaignStatus.Running,
  1762.                 name: CampaignActions.Run,
  1763.                 conditions: [advertiserNotBlocked, isAdvertiser, isPremiumAdvertiser, adApproved]
  1764.               }),
  1765.             new StateAction<any>(
  1766.               {
  1767.                 to: CampaignStatus.Draft,
  1768.                 name: CampaignActions.EditRequested,
  1769.                 conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser]
  1770.               })
  1771.           ]
  1772.         }),
  1773.         new State<any>({
  1774.           name: CampaignStatus.Running,
  1775.           actions: [
  1776.             endAction,
  1777.             blockAction,
  1778.             new StateAction<any>(
  1779.               {
  1780.                 name: CampaignActions.Edit,
  1781.                 conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved]
  1782.               }),
  1783.             new StateAction<any>({
  1784.               to: CampaignStatus.Stopped,
  1785.               name: CampaignActions.Stop,
  1786.               conditions: [advertiserNotBlocked, isAdvertiser]
  1787.             }),
  1788.           ]
  1789.         }),
  1790.         new State<any>({
  1791.           name: CampaignStatus.Stopped,
  1792.           actions: [
  1793.             endAction,
  1794.             blockAction,
  1795.             new StateAction<any>(
  1796.               {
  1797.                 name: CampaignActions.Edit,
  1798.                 conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved]
  1799.               }),
  1800.             new StateAction<any>(
  1801.               {
  1802.                 to: CampaignStatus.Running,
  1803.                 name: CampaignActions.Run,
  1804.                 conditions: [advertiserNotBlocked, adApproved, isAdvertiser, haveFunds]
  1805.               })
  1806.           ]
  1807.         }),
  1808.         new State<any>({
  1809.           name: CampaignStatus.SystemStopped,
  1810.           actions: [
  1811.             endAction,
  1812.             blockAction,
  1813.             new StateAction<any>(
  1814.               {
  1815.                 to: CampaignStatus.Stopped,
  1816.                 name: CampaignActions.EditDone,
  1817.                 conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved]
  1818.               }),
  1819.             new StateAction<any>(
  1820.               {
  1821.                 name: CampaignActions.Edit,
  1822.                 conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved]
  1823.               })
  1824.           ]
  1825.         }),
  1826.         new State<any>({
  1827.           name: CampaignStatus.Rejected,
  1828.           actions: [
  1829.             endAction,
  1830.             blockAction,
  1831.             new StateAction<any>(
  1832.               {
  1833.                 to: CampaignStatus.Draft,
  1834.                 name: CampaignActions.EditRequested,
  1835.                 conditions: [advertiserNotBlocked, isAdvertiser]
  1836.               })
  1837.           ]
  1838.         }),
  1839.         new State<any>({
  1840.           name: CampaignStatus.Blocked,
  1841.           actions: [
  1842.             new StateAction<any>(
  1843.               {
  1844.                 to: previousState,
  1845.                 name: CampaignActions.UnBlock,
  1846.                 conditions: [isAdminUser]
  1847.               })
  1848.           ]
  1849.         }),
  1850.         new State<any>({
  1851.           name: CampaignStatus.Ended,
  1852.           actions: []
  1853.         }),
  1854.       ],
  1855.       (entity, status, context) => {
  1856.         return Campaign.changeStatus(entity, status, context);
  1857.       });
  1858.   };
  1859.  
  1860.   const isAdvertiserBlock = (entity: any, context: any): Promise<boolean> => {
  1861.     if (context) {
  1862.       return Promise.resolve(context.isAdvertiserBlock === true);
  1863.     }
  1864.  
  1865.     return Promise.resolve(false);
  1866.   };
  1867.  
  1868.   const adApproved = (entity: any, context: any): Promise<boolean> => {
  1869.     return Promise.resolve(context !== undefined &&
  1870.       context.ad !== undefined &&
  1871.       context.ad._status.status === AdStatus.Approved
  1872.     );
  1873.   };
  1874.  
  1875.   const adNotRejectedOrBlocked = (entity: any, context: any): Promise<boolean> => {
  1876.     return Promise.resolve(context !== undefined &&
  1877.       context.ad !== undefined &&
  1878.       context.ad._status.status !== AdStatus.Rejected &&
  1879.       context.ad._status.status !== AdStatus.Blocked
  1880.     );
  1881.   };
  1882.  
  1883.   const adSubmitted = (entity: any, context: any): Promise<boolean> => {
  1884.     return Promise.resolve(context.ad._status.status === AdStatus.Submitted);
  1885.   };
  1886.  
  1887.   const adNew = (entity: any, context: any): Promise<boolean> => {
  1888.     return Promise.resolve(context.ad._status.status === AdStatus.Draft);
  1889.   };
  1890.  
  1891.   const isQuickAd = (entity: any, context: any): Promise<boolean> => {
  1892.     return Promise.resolve(context !== undefined &&
  1893.       context.ad !== undefined &&
  1894.       context.ad.originFrom !== undefined && context.ad.originFrom !== '' &&
  1895.       context.ad._status.status !== AdStatus.Approved
  1896.     );
  1897.   };
  1898.  
  1899.   const notPremiumAdvertiser = (entity: any, context: any): Promise<boolean> => {
  1900.     return Promise.resolve(entity.isFixedInventory !== true);
  1901.   };
  1902.  
  1903.   const isPremiumAdvertiser = (entity: any, context: any): Promise<boolean> => {
  1904.     return Promise.resolve(entity.isFixedInventory === true);
  1905.   };
  1906.  
  1907.   const isAdminUser = (entity: any, context: any): Promise<boolean> => {
  1908.     return Promise.resolve(context !== undefined && context.isadmin === true);
  1909.   };
  1910.  
  1911.   const isAdvertiser = (entity: any, context: any): Promise<boolean> => {
  1912.     return Promise.resolve(
  1913.       context &&
  1914.       context.user &&
  1915.       context.user.advertiserId.toString() === entity.advertiserId.toString());
  1916.   };
  1917.  
  1918.   const advertiserNotBlocked = (entity: any, context: any): Promise<boolean> => {
  1919.     if (context &&
  1920.       context.advertiser &&
  1921.       context.advertiser.blocked === true) {
  1922.       return Promise.resolve(false);
  1923.     }
  1924.     return Promise.resolve(true);
  1925.   };
  1926.  
  1927.   const haveFunds = (entity: any, context: any): Promise<boolean> => {
  1928.     return app.models.wallet.findOne({ where: { advertiserId: context.user.advertiserId } })
  1929.       .then((wallet) => {
  1930.         if (!wallet) {
  1931.           return Promise.resolve(false);
  1932.         }
  1933.         return Promise.resolve(wallet.balance > 0);
  1934.       });
  1935.   };
  1936.  
  1937.   const previousState = (entity: any, context: any): Promise<string> => {
  1938.     if (entity.statusLog) {
  1939.       let lastStatusLog = _.last(entity.statusLog);
  1940.       if (lastStatusLog) {
  1941.         return lastStatusLog.previous;
  1942.       }
  1943.     }
  1944.  
  1945.     return '';
  1946.   };
  1947.  
  1948.   const approveCampaignAd = (entity: any, context: any): Promise<any> => {
  1949.     ////NOTE: update AD to approved... Approve AD if campaign is approved
  1950.     if (context.ad._status.status === AdStatus.Submitted || context.ad._status.status === AdStatus.Rejected) {
  1951.       return app.models.bpad.executeAction(entity.ad.id, AdActions.Approve, null, context.options)
  1952.         .then(x => {
  1953.           return updateCampaingAd(entity);
  1954.         });
  1955.     }
  1956.  
  1957.     return Promise.resolve(entity);
  1958.   };
  1959.  
  1960.   const rejectCampaignAd = (entity: any, context: any): Promise<any> => {
  1961.     ////NOTE: update AD to approved...
  1962.     if (context.ad._status.status === AdStatus.Submitted) {
  1963.       return app.models.bpad.executeAction(entity.ad.id, AdActions.Reject, null, context.options)
  1964.         .then(x => {
  1965.           return updateCampaingAd(entity);
  1966.         });
  1967.     }
  1968.  
  1969.     return Promise.resolve(entity);
  1970.   };
  1971.  
  1972.   const submitCampaignAd = (entity: any, context: any): Promise<any> => {
  1973.     if (context.ad._status.status === AdStatus.Draft) {
  1974.       //////// AD state machine
  1975.       ////(id:  string, action:  string, data:  any, options:  any)
  1976.       return app.models.bpad.executeAction(entity.ad.id, AdActions.Submit, null, context.options)
  1977.         .then(x => {
  1978.           return updateCampaingAd(entity);
  1979.         });
  1980.     }
  1981.     return Promise.resolve(entity);
  1982.   };
  1983.  
  1984.   const prepareStateMachineData = (options: any): any => {
  1985.     let authHeader = Utils.getRequestAuthHeaderSync(options);
  1986.     let contextData = {
  1987.       options: options,
  1988.       user: authHeader.user,
  1989.       roles: authHeader.roles,
  1990.       isadmin: authHeader.isAdminUser,
  1991.       isAdvertiserBlock: options.isAdvertiserBlock,
  1992.  
  1993.       submitted: null,
  1994.       campaign: null,
  1995.       ad: null
  1996.     };
  1997.  
  1998.     return contextData;
  1999.   };
  2000.  
  2001.   const updateCampaingAd = (campaign: any): Promise<any> => {
  2002.     return app.models.bpad.findById(campaign.ad.id)
  2003.       .then(newAd => {
  2004.         return campaign.updateAttributes({ ad: newAd }).then((err, instance) => {
  2005.           // if (err) {
  2006.           //   return Promise.reject(err);
  2007.           // }
  2008.  
  2009.           return instance;
  2010.         });
  2011.       });
  2012.   };
  2013.  
  2014.   Campaign.exhausted = (campaignId: string): Promise<void> => {
  2015.     // tslint:disable-next-line:no-console
  2016.     console.log('__  Campaign.exhausted -- campaignId: ', campaignId);
  2017.     // let cmpId = JSON.parse(campaignId).id;
  2018.     return new Promise<void>((resolve, reject) => {
  2019.       winston.info(`Campaign ${campaignId} is exhausted.`, { campaignId: campaignId });
  2020.  
  2021.       waterfall([
  2022.         (next) => {
  2023.           // app.models.campaign.findOne({where:{_id: campaignId}}, (error, campaign) => {
  2024.           app.models.campaign.findOne({ where: { id: campaignId } }, (error, campaign) => {
  2025.             if (!error && !campaign) {
  2026.               error = new Error(`Campaign ${campaignId} not found for campaign exhausted event.`);
  2027.             }
  2028.             winston.info('Find campaign finished.', error, campaign);
  2029.             next(error, campaign);
  2030.           });
  2031.         },
  2032.         (campaign, next) => {
  2033.           app.models.advertiser.findOne({ where: { id: campaign.advertiserId } }, (error, advertiser) => {
  2034.             winston.info('__ campaign.exausted -- find advertiser ', advertiser);
  2035.             if (!error && !advertiser) {
  2036.               error = new Error(`Advertiser ${campaign.advertiserId} not found for campaign exhausted event.`);
  2037.             }
  2038.             next(error, campaign, advertiser);
  2039.           });
  2040.         },
  2041.         (campaign, advertiser, next) => {
  2042.           winston.info('Find advertiser-user started.');
  2043.           app.models.adnexaUser.findOne({ where: { advertiserId: advertiser.id } }, (error, user) => {
  2044.             winston.info('Find advertiser-user finished.', user ? user.email : null);
  2045.             next(error, campaign, advertiser, user);
  2046.           });
  2047.         },
  2048.         (campaign, advertiser, user, next) => {
  2049.  
  2050.           if (campaign.isFixedInventory) {
  2051.             Campaign.changeStatus(campaign, CampaignStatus.Ended, { user: { id: 'admin' } });
  2052.           } else {
  2053.             Campaign.changeStatus(campaign, CampaignStatus.SystemStopped, { user: { id: 'admin' } });
  2054.           }
  2055.           next(null, campaign, advertiser, user);
  2056.         },
  2057.         (campaign, advertiser, user, next) => {
  2058.           if (user) {
  2059.             ejs.renderFile(path.resolve(__dirname, '../views/campaignExhausted.ejs'),
  2060.               { fullName: user.fullName, title: campaign.title, url: client.url + 'campaigns/' + campaign.id, img: client.url + 'assets/img/header.png' }, {}, (error, html) => {
  2061.                 Utils.sendMail({
  2062.                   to: user.email,
  2063.                   subject: 'Your campaign has been stopped',
  2064.                   html
  2065.                 }, (err: Error) => {
  2066.                   if (err) { return winston.info('> error sending campaign exhausted email'); }
  2067.                   winston.info('> sending campaign exhausted email to:', user.email);
  2068.                   next(null);
  2069.                 });
  2070.               });
  2071.           } else {
  2072.             next(null);
  2073.           }
  2074.         }
  2075.       ],
  2076.         (error, result) => {
  2077.           if (error) {
  2078.             winston.error(`Error handling the campaign exhausted event for campaing ${campaignId}`, error);
  2079.             reject(error);
  2080.           } else {
  2081.             resolve();
  2082.           }
  2083.         });
  2084.     });
  2085.   };
  2086.  
  2087.   Campaign.exhaustedSoon = (campaignId: string): Promise<void> => {
  2088.     return new Promise<void>((resolve, reject) => {
  2089.       waterfall([
  2090.         (next) => {
  2091.           winston.info('Find campaign started.', campaignId);
  2092.           app.models.campaign.findOne({ where: { _id: campaignId } }, (error, campaign) => {
  2093.             winston.info('Find campaign finished.', campaign);
  2094.             next(error, campaign);
  2095.           });
  2096.         },
  2097.         (campaign, next) => {
  2098.           winston.info('Find advertiser started.');
  2099.           app.models.advertiser.findOne({ where: { _id: campaign.advertiserId } }, (error, advertiser) => {
  2100.             winston.info('Find advertiser finished.', advertiser);
  2101.             next(error, campaign, advertiser);
  2102.           });
  2103.         },
  2104.         (campaign, advertiser, next) => {
  2105.           winston.info('Find advertiser-user started.');
  2106.           app.models.adnexaUser.findOne({ where: { advertiserId: advertiser.id } }, (error, user) => {
  2107.             winston.info('Find advertiser-user finished.', user ? user.email : null);
  2108.             next(error, campaign, advertiser, user);
  2109.           });
  2110.         },
  2111.         (campaign, advertiser, user, next) => {
  2112.           if (user) {
  2113.             ejs.renderFile(path.resolve(__dirname, '../views/campaignExhaustedSoon.ejs'),
  2114.               { fullName: user.fullName, title: campaign.title, url: client.url + 'campaigns/' + campaign.id, img: client.url + 'assets/img/header.png' }, {}, (error, html) => {
  2115.                 if (error) {
  2116.                   winston.error(error);
  2117.                 }
  2118.                 Utils.sendMail({
  2119.                   to: user.email,
  2120.                   subject: 'Your Budget or Impression limit has nearly been reached',
  2121.                   html
  2122.                 }, (err: Error) => {
  2123.                   if (err) { return winston.info('> error sending campaign exhausted soon email'); }
  2124.                   winston.info('> sending campaign exhausted soon to:', user.email);
  2125.                   next(null);
  2126.                 });
  2127.               });
  2128.           } else {
  2129.             next(null);
  2130.           }
  2131.         }
  2132.       ],
  2133.         (error, result) => {
  2134.           if (error) {
  2135.             winston.error(error);
  2136.             reject(error);
  2137.           } else {
  2138.             resolve();
  2139.           }
  2140.         });
  2141.     });
  2142.   };
  2143.  
  2144.   Campaign.getTopCPICampaigns = (num: number, options: any, cb: any) => {
  2145.  
  2146.     let authObj = Utils.getRequestAuthHeaderSync(options);
  2147.     let advertiserId;
  2148.  
  2149.     if (authObj.isAdminUser) {
  2150.       advertiserId = null;
  2151.     } else {
  2152.       advertiserId = authObj.user.advertiserId;
  2153.     }
  2154.  
  2155.     let collection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
  2156.  
  2157.     let topCPIQuery = TopCampaignsQuery(advertiserId, num, ['running'], 'CPI');
  2158.  
  2159.     collection.aggregate(topCPIQuery.query, (error, campaigns) => {
  2160.       if (error) {
  2161.         cb(error);
  2162.       }
  2163.  
  2164.       cb(null, campaigns);
  2165.     });
  2166.   };
  2167.  
  2168.   Campaign.getTopImpressionsCampaigns = (num: number, options: any, cb: any) => {
  2169.  
  2170.     let authObj = Utils.getRequestAuthHeaderSync(options);
  2171.     let advertiserId;
  2172.  
  2173.     if (authObj.isAdminUser) {
  2174.       advertiserId = null;
  2175.     } else {
  2176.       advertiserId = authObj.user.advertiserId;
  2177.     }
  2178.  
  2179.     let collection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
  2180.  
  2181.     let topCPIQuery = TopCampaignsQuery(advertiserId, num, ['running'], 'IMPRESSIONS');
  2182.  
  2183.     collection.aggregate(topCPIQuery.query, (error, campaigns) => {
  2184.       if (error) {
  2185.         cb(error);
  2186.       }
  2187.  
  2188.       cb(null, campaigns);
  2189.     });
  2190.   };
  2191.  
  2192.   Campaign.getTopCPIRoutes = (num: number, options: any, cb: any) => {
  2193.  
  2194.     let authObj = Utils.getRequestAuthHeaderSync(options);
  2195.     let advertiserId;
  2196.  
  2197.     if (authObj.isAdminUser) {
  2198.       advertiserId = null;
  2199.     } else {
  2200.       advertiserId = authObj.user.advertiserId;
  2201.     }
  2202.  
  2203.     let collection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
  2204.  
  2205.     let topCPIRouteQuery = TopRoutesQuery(advertiserId, num, ['running'], 'CPI');
  2206.  
  2207.     collection.aggregate(topCPIRouteQuery.query, (error, routes) => {
  2208.       if (error) {
  2209.         cb(error);
  2210.       }
  2211.  
  2212.       cb(null, routes);
  2213.     });
  2214.   };
  2215.  
  2216.   Campaign.getTopImpressionsRoutes = (num: number, options: any, cb: any) => {
  2217.  
  2218.     let authObj = Utils.getRequestAuthHeaderSync(options);
  2219.     let advertiserId;
  2220.  
  2221.     if (authObj.isAdminUser) {
  2222.       advertiserId = null;
  2223.     } else {
  2224.       advertiserId = authObj.user.advertiserId;
  2225.     }
  2226.  
  2227.     let collection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
  2228.  
  2229.     let topCPIRouteQuery = TopRoutesQuery(advertiserId, num, ['running'], 'IMPRESSIONS');
  2230.  
  2231.     collection.aggregate(topCPIRouteQuery.query, (error, routes) => {
  2232.       if (error) {
  2233.         cb(error);
  2234.       }
  2235.  
  2236.       cb(null, routes);
  2237.     });
  2238.   };
  2239.  
  2240.   const prepareStateMachineContext = (options: any): any => {
  2241.     let authHeader = Utils.getRequestAuthHeaderSync(options);
  2242.     let data = {
  2243.       user: authHeader.user,
  2244.       isAdminUser: authHeader.isAdminUser,
  2245.       isAdvertiserBlock: options.isAdvertiserBlock,
  2246.       roles: authHeader.roles,
  2247.       ad: null
  2248.     };
  2249.  
  2250.     return data;
  2251.   };
  2252.  
  2253.   Campaign.advertiserHasCampaign = (fixedInventory: boolean, advertiserId: string, options: any, cb: any) => {
  2254.     Campaign.count({
  2255.       advertiserId: advertiserId,
  2256.       isFixedInventory: fixedInventory
  2257.     }, (error, count) => {
  2258.       if (error) {
  2259.         cb(error);
  2260.       }
  2261.       if (count > 0) {
  2262.         cb(null, true);
  2263.       }
  2264.       else {
  2265.         cb(null, false);
  2266.       }
  2267.     });
  2268.   };
  2269.  
  2270.   // Warning: Current implementation does not disable methods generated by relations. See the GitHub issue (https://github.com/strongloop/loopback/issues/2860). - This code below will disable all methods except methods that are in array.
  2271.   const remoteMethod = new RemoteMethod(Campaign,
  2272.     [
  2273.       'reach',
  2274.       'getTopImpressionsRoutes',
  2275.       'getTopImpressionsCampaigns',
  2276.       'getTopCPIRoutes',
  2277.       'getTopCPICampaigns',
  2278.       'getStatisticsSummary',
  2279.       'getCurrentStatus',
  2280.       'getCountByStatus',
  2281.       'findOne',
  2282.       'find',
  2283.       'findById',
  2284.       //'won',
  2285.       'getImpressionsSummary',
  2286.       'getCampaignStatisticsTotal',
  2287.       'advertiserHasCampaign',
  2288.       'executeAction',
  2289.       'actions',
  2290.       'create',
  2291.       'patchAttributes',
  2292.       'replaceById',
  2293.       'matchingPublishers'
  2294.     ]);
  2295.   remoteMethod.disableMethods();
  2296. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement