Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /// <reference path="../../typings/index.d.ts" />
- import * as path from 'path';
- import app from '../app';
- import * as winston from 'winston';
- import * as async from 'async';
- import * as waterfall from 'async-waterfall';
- import { OfferingStatus } from 'aws-sdk/clients/devicefarm';
- import * as Promise from 'bluebird';
- import * as _ from 'lodash';
- import * as moment from 'moment';
- import { type } from 'os';
- import * as ejs from 'ejs';
- import { ImpressionsSummaryQuery, TopCampaignsQuery, TopRoutesQuery } from './campaign.queries';
- import { CampaignConst } from './campaign-const';
- import CampaignStatus = CampaignConst.CampaignStatus;
- import CampaignActions = CampaignConst.CampaignActions;
- import CampaignStatisticsPeriod = CampaignConst.CampaignStatisticsPeriod;
- import { AdConst } from './ad-const';
- import AdStatus = AdConst.AdStatus;
- import AdActions = AdConst.AdActions;
- import { WorkFlowStateMachine } from '../module/workflow-state';
- import StateAction = WorkFlowStateMachine.Action;
- import State = WorkFlowStateMachine.State;
- import StateMachine = WorkFlowStateMachine.StateMachine;
- import WorkflowResults = WorkFlowStateMachine.Results;
- import BadRequest from '../errors/BadRequest';
- import NotFound from '../errors/NotFound';
- import InvalidCampaign from '../errors/InvalidCampaign';
- import RemoteMethod from '../helpers/RemoteMethod';
- import { Utils } from '../helpers/utils';
- const ObjectId = require('mongodb').ObjectId;
- const appSettings = require('../app.settings');
- /* CAMPAIGN WORKFLOW */
- /*Once a Campaign is Submitted, Advertiser can Run the Campaign.
- Once a Campaign is Running Advertiser can Stop the Campaign.
- After Campaign is Stopped, Advertiser can Run it again.
- A Campaign should be Ended when the defined Flight period runs out. */
- export = (Campaign: any) => {
- Campaign.validatesUniquenessOf('title', { scopedTo: ['advertiserId'] });
- // Each time the Campaign is retrieved by Front-end, validation on flight period is needed
- // If period expired, Campaign status should automatically change to -> ended
- let client = app.get('client');
- Campaign.afterRemote('find', (ctx, instance, next) => {
- // Counter is needed as an indicator when to call fn next() to proceed with find method
- let counter = 0;
- if (ctx.args &&
- ctx.args.filter &&
- ctx.args.filter.include &&
- ctx.args.filter.include.lastIndexOf('campaignStatistics') > -1) {
- let promise = [];
- instance.forEach(campaign => {
- promise.push(
- app.models.campaignStatistics.findOne({
- where: {
- campaignId: campaign.id,
- periodType: CampaignStatisticsPeriod.AllTime
- }
- })
- .then(x => {
- if (!x) {
- return createStatistics(campaign, CampaignStatisticsPeriod.AllTime);
- }
- return x;
- })
- .then(entity => {
- entity.days = Campaign.getDays(campaign);
- campaign.__data.campaignStatistics = entity;
- })
- );
- });
- Promise.all(promise).then(values => {
- next();
- }).catch(err => {
- next(err);
- });
- } else {
- next();
- }
- });
- Campaign.beforeRemote('create', (ctx, model, next) => {
- ctx.req.body._status = {
- status: CampaignStatus.Draft,
- lastChangeDate: moment.utc()
- };
- let contextData = prepareStateMachineContext(ctx.req);
- ctx.req.body.advertiserId = contextData.user.advertiserId;
- validateCampaign(ctx.req.body).then(res => {
- ////NOTE: on create check if Edit action is allowed...
- initializeStateMachineByInstance(ctx.req.body, contextData)
- .then(machine => {
- return machine.isActionAllowed(CampaignActions.Edit, contextData);
- })
- .then(allowed => {
- if (!allowed) {
- let error = new BadRequest(BadRequest.AD_BAD_REQUEST);
- next(error);
- } else {
- next();
- }
- })
- .catch(err => {
- return next(err);
- });
- })
- .catch(err => {
- next(err);
- });
- });
- Campaign.beforeRemote('**', (ctx, instance, next) => {
- if (ctx.methodString === 'campaign.replaceById' ||
- ctx.methodString === 'campaign.patchAttributes') {
- /////Check if update is allowed.
- updateCampaign(ctx)
- .then((result) => {
- return validateCampaign(ctx.req.body).then(res => {
- next();
- return null;
- })
- .catch((err) => {
- return next(new Error(err));
- });
- })
- .catch((err) => {
- return next(err);
- });
- } else {
- next();
- }
- });
- const validateCampaign = (campaign: any): Promise => {
- return new Promise((resolve, reject) => {
- let startDate = moment(campaign.startDate);
- let endDate = moment(campaign.endDate);
- let maxEndDate = moment(campaign.startDate).add(appSettings.campaign.maxPeriodLength.amount, appSettings.campaign.maxPeriodLength.units);
- if (!validateUrls(campaign)) {
- reject(new InvalidCampaign('Content URL-s should be from the same origin.'));
- return;
- } else if (endDate.isBefore(startDate)) {
- let error = new InvalidCampaign('Start date must be before end date.');
- reject(error);
- return;
- } else if (endDate.isAfter(maxEndDate)) {
- let error = new InvalidCampaign('Maximum campaign duration is ' + appSettings.campaign.maxPeriodLength.amount + ' ' + appSettings.campaign.maxPeriodLength.units);
- reject(error);
- return;
- } else if (!campaign.isFixedInventory && !campaign.totalBudget) {
- // for standard campaign total budget is required, for fixed inv it is not
- let error = new InvalidCampaign('Total budget is required');
- reject(error);
- } else if (campaign.maxDailyBudget && campaign.maxCPI > campaign.maxDailyBudget) {
- let error = new InvalidCampaign('Maximum CPI can not be greater than daily budget');
- reject(error);
- } else if (campaign.totalBudget && campaign.maxCPI > campaign.totalBudget) {
- let error = new InvalidCampaign('Maximum CPI can not be greater than total budget.');
- reject(error);
- } else if (campaign.totalBudget && campaign.maxDailyBudget && campaign.maxDailyBudget > campaign.totalBudget) {
- let error = new InvalidCampaign('Daily budget can not be greater than total budget.');
- reject(error);
- } else if (campaign.maxCPI && campaign.maxCPI < 0.1) {
- let error = new InvalidCampaign('Maximum CPI can not be less than 0.1.');
- reject(error);
- } else {
- Promise.join(
- app.models.advertiser.findById(campaign.advertiserId),
- app.models.campaignStatistics.findOne({
- where: {
- campaignId: campaign.id,
- periodType: CampaignStatisticsPeriod.AllTime
- }
- })).spread((advertiser, state) => {
- if (state && state.impressions && campaign.impressionsLimit && state.impressions > campaign.impressionsLimit) {
- let error = new InvalidCampaign('Impressions limit exceeded.');
- return reject(error);
- } else if (campaign.isFixedInventory) {
- if (!advertiser.premium) {
- let error = new InvalidCampaign('Fixed inventory campaigns can be created only by premium users.');
- return reject(error);
- } else {
- return resolve();
- }
- } else {
- return resolve();
- }
- })
- .catch(err => {
- return reject(err);
- });
- }
- });
- };
- const validateUrls = (campaign: any): boolean => {
- let url = process.env.BEMAD_URL;
- if (url && campaign && campaign.ads && campaign.ads._content) {
- for (let content of campaign.ads._content) {
- if (content && content.imageUrl && !content.imageUrl.startsWith(`${url}/api/containers/`)) {
- return false;
- }
- }
- }
- return true;
- };
- const updateCampaign = (ctx: any): Promise => {
- return new Promise((resolve, reject) => {
- ///let bpad = app.models.bpad;
- let authHeader = Utils.getRequestAuthHeaderSync(ctx.req);
- let contextData = {
- user: authHeader.user,
- roles: authHeader.roles,
- isadmin: authHeader.isAdminUser,
- campaign: null,
- ad: null,
- advertiser: null
- };
- Campaign.findById(ctx.req.params.id)
- .then((campaign) => {
- if (!campaign) {
- let error = new NotFound(NotFound.CAMPAIGN_NOT_FOUND);
- return reject(error);
- }
- ctx.req.body = _.assign({}, campaign.__data, ctx.req.body);
- ctx.req.body._status = campaign.__data._status;
- ctx.req.body.statusLog = campaign.__data.statusLog;
- return initializeStateMachineByInstance(ctx.req.body, contextData);
- // return bpad.findById(campaign.ad.id).then(ad => {
- // contextData.ad = ad;
- // return contextData;
- // });
- })
- // .then((x) => {
- // let stateMachine = Campaign.workflowStateMachineDefinition();
- // return stateMachine.initialize(x.campaign._status.status, x.campaign, x);
- // })
- .then(machine => {
- return machine.isActionAllowed(CampaignActions.Edit, contextData);
- })
- .then(allowed => {
- if (!allowed) {
- let error = new BadRequest(BadRequest.CAMPAIGN_EDIT_NOT_ALLOWED);
- reject(error);
- return;
- } else {
- resolve();
- }
- })
- .catch(err => {
- reject(err);
- });
- });
- };
- Campaign.getCampaignStatisticsTotal = (id, options) => {
- return Promise.join(
- Campaign.findById(id),
- app.models.campaignStatistics.findOne({
- where: {
- campaignId: id,
- periodType: CampaignStatisticsPeriod.AllTime
- }
- })
- ).spread((campaign, state) => {
- if (state) {
- state.days = Campaign.getDays(campaign);
- return {
- avgCPI: state.avgCPI,
- impressions: state.impressions,
- costs: state.costs,
- days: state.days,
- campaignId: state.campaignId
- };
- }
- return state;
- });
- // return app.models.campaignStatistics.findOne({
- // where: {
- // campaignId: id,
- // periodType: CampaignStatisticsPeriod.AllTime
- // }
- // });
- };
- Campaign.afterRemote('**', (ctx, model, next) => {
- if (ctx.methodString !== 'campaign.find' || (!ctx.args || !ctx.args.filter || !ctx.args.filter['count'])) {
- return next();
- } else {
- Campaign.count(ctx.args.filter.where)
- .then((count) => {
- ctx.res.set('Access-Control-Expose-Headers', 'x-total-count');
- ctx.res.set('X-Total-Count', count);
- return next();
- })
- .catch((error) => {
- return next();
- });
- }
- });
- Campaign.getCountByStatus = (options, cb) => {
- let authHeader = Utils.getRequestAuthHeaderSync(options);
- let campaignCollection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
- let advertiserId = authHeader.user.advertiserId;
- campaignCollection.aggregate([
- {
- $match: { advertiserId: ObjectId(advertiserId) }
- },
- {
- $group:
- {
- _id: '$_status.status',
- num: { $sum: 1 }
- }
- },
- {
- $project:
- {
- '_id': 0,
- 'count': '$num',
- 'status': '$_id'
- }
- }
- ], (err, data) => {
- if (err) {
- winston.info('Count campaigns by status error => ', err);
- cb(err);
- }
- cb(null, data);
- });
- };
- Campaign.getStatisticsSummary = (options, cb) => {
- let authHeader = Utils.getRequestAuthHeaderSync(options);
- let campaignCollection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
- let advertiserId = authHeader.user.advertiserId;
- campaignCollection.aggregate([
- {
- $match: { advertiserId: ObjectId(advertiserId) }
- },
- {
- $lookup:
- {
- from: 'campaignStatistics',
- localField: '_id',
- foreignField: 'campaignId',
- as: 'campaignStatistics'
- }
- },
- {
- $addFields: {
- campaignStatistics: {
- $arrayElemAt: [{
- $filter: {
- 'input': '$campaignStatistics',
- 'as': 'stat',
- 'cond': { '$eq': ['$$stat.periodType', CampaignStatisticsPeriod.AllTime] }
- }
- }, 0]
- }
- },
- },
- {
- $unwind: '$campaignStatistics'
- },
- {
- $addFields:
- {
- 'calcDays': { $add: [{ $floor: { $divide: [{ $subtract: [new Date(), '$startDate',] }, 60 * 60 * 24 * 1000] } }, 2] },
- 'allDays': { $add: [{ $floor: { $divide: [{ $subtract: ['$endDate', '$startDate',] }, 60 * 60 * 24 * 1000] } }, 2] }
- }
- },
- {
- $group:
- {
- _id: '$_status.status',
- costs: { $sum: '$campaignStatistics.costs' },
- impressions: { $sum: '$campaignStatistics.impressions' },
- passedDays: { $sum: { $cond: [{ $gt: ['$calcDays', 0] }, { $cond: [{ $gt: ['$calcDays', '$allDays'] }, '$allDays', '$calcDays'] }, 0] } },
- budget: { $sum: '$totalBudget' }
- }
- },
- {
- $project:
- {
- '_id': 0,
- 'status': '$_id',
- 'costs': { $divide: [{ $trunc: { $multiply: ['$costs', 100] } }, 100] },
- 'impressions': 1,
- 'avgCPI':
- {
- $cond:
- {
- if: { $gt: ['$impressions', 0] },
- then: { $divide: [{ $trunc: { $multiply: [{ $divide: ['$costs', '$impressions'] }, 100] } }, 100] },
- else: 0
- }
- },
- 'passedDays': 1,
- 'budget': 1
- }
- }
- ], (err, data) => {
- if (err) {
- winston.info('Count campaigns by status error => ', err);
- cb(err);
- }
- cb(null, data);
- });
- };
- Campaign.getImpressionsSummary = (id: any, filter: any, options: any, cb: any) => {
- let authHeader = Utils.getRequestAuthHeaderSync(options);
- let bpCardCollection = app.models.boardingCard.getDataSource().connector.collection(app.models.boardingCard.modelName);
- let queryDef = ImpressionsSummaryQuery(id, filter);
- let advertiserId = authHeader.user.advertiserId;
- waterfall([
- (next) => {
- Campaign.findById(id, { fields: { advertiserId: true } }, (error, campaign) => {
- if (!campaign || campaign.advertiserId.toString() !== advertiserId.toString()) {
- let e: { status?: number } = new Error('Can\'t find campaign for advertiser');
- e.status = 404;
- next(e);
- return;
- }
- next(error);
- });
- },
- (next) => {
- bpCardCollection.aggregate(queryDef.queryCount, (error, count) => {
- next(error, (count && count.length) ? count[0].total : 0);
- });
- },
- (count, next) => {
- if (count === 0) {
- cb(null, null, count);
- return;
- }
- bpCardCollection.aggregate(queryDef.query, (error, impressions) => {
- cb(error, impressions, count);
- });
- }
- ]);
- };
- Campaign.updateStatistics = (campaign, boardingCard, amount, timestamp) => {
- Promise.join(
- findAndUpdateStatistic(campaign, boardingCard, amount, CampaignStatisticsPeriod.AllTime, timestamp),
- findAndUpdateStatistic(campaign, boardingCard, amount, CampaignStatisticsPeriod.Monthly, timestamp),
- findAndUpdateStatistic(campaign, boardingCard, amount, CampaignStatisticsPeriod.Daily, timestamp),
- );
- };
- const findAndUpdateStatistic = (campaign, boardingCard, amount, periodType, timestamp): Promise<CampaignStatistics> => {
- let condition: any = {
- where: {
- campaignId: campaign.id,
- periodType: periodType
- }
- };
- if (periodType !== CampaignStatisticsPeriod.AllTime) {
- let period = createPeriodDates(periodType, timestamp);
- condition = {
- where: {
- campaignId: campaign.id,
- periodType: periodType,
- periodFrom: period.periodFrom,
- periodTo: period.periodTo
- }
- };
- }
- return app.models.campaignStatistics.findOne(condition)
- .then(x => {
- if (!x) {
- return createStatistics(campaign, periodType, timestamp);
- }
- return x;
- })
- .then(entity => {
- return updateCampaignStatistics(entity, boardingCard, amount, campaign);
- });
- };
- const updateCampaignStatistics = (stat, boardingCard, amount, campaign) => {
- let newState: any = {
- advertiserId: campaign.advertiserId,
- periodType: stat.periodType,
- periodFrom: stat.periodFrom,
- periodTo: stat.periodTo,
- costs: _.round(stat.costs + amount, 2),
- impressions: stat.impressions + 1,
- avgCPI: 0,
- routeStatistics: stat.routeStatistics
- };
- newState.avgCPI = _.round(newState['costs'] / newState['impressions'], 2);
- let indx = _.findIndex(stat.routeStatistics, { route: boardingCard.orig + '-' + boardingCard.dest });
- if (indx === -1) {
- ////newState['routeStatistics'] = newState['routeStatistics'].concat(statistics['routeStatistics']);
- newState.routeStatistics.push({
- route: boardingCard.orig + '-' + boardingCard.dest,
- costs: _.round(amount, 2),
- impressions: 1,
- avgCPI: _.round(amount, 2)
- });
- } else {
- let newRouteState: any = {};
- newRouteState.route = boardingCard.orig + '-' + boardingCard.dest;
- newRouteState.costs = _.round(stat.routeStatistics[indx].costs + amount, 2);
- newRouteState.impressions = stat.routeStatistics[indx].impressions + 1;
- newRouteState.avgCPI = _.round(newRouteState.costs / newRouteState.impressions, 2);
- stat.routeStatistics.splice(indx, 1, newRouteState);
- // stat.routeStatistics.push(newRouteState);
- newState.routeStatistics = stat.routeStatistics;
- }
- return stat.updateAttributes(newState);
- };
- const createStatistics = (campaign, periodType, date?): Promise => {
- let period = createPeriodDates(periodType, date);
- let stat = {
- periodType: periodType,
- periodFrom: period.periodFrom,
- periodTo: period.periodTo,
- campaignId: campaign.id,
- advertiserId: campaign.advertiserId,
- avgCPI: 0,
- costs: 0,
- impressions: 0,
- routeStatistics: []
- };
- return app.models.campaignStatistics.create(stat);
- };
- const createPeriodDates = (periodType, date?): any => {
- let result = {
- periodFrom: null,
- periodTo: null
- };
- if (date === undefined || date === null) {
- date = new Date();
- }
- if (periodType === CampaignStatisticsPeriod.Monthly) {
- result.periodFrom = moment(date).startOf('month').toDate();
- result.periodTo = moment(date).endOf('month').toDate();
- }
- if (periodType === CampaignStatisticsPeriod.Daily) {
- result.periodFrom = moment(date).startOf('day').toDate();
- result.periodTo = moment(date).endOf('day').toDate();
- }
- return result;
- };
- Campaign.getCurrentStatus = (campaign, options) => {
- let bpad = app.models.bpad;
- let authHeader = Utils.getRequestAuthHeaderSync(options);
- let data = {
- user: authHeader.user,
- roles: authHeader.roles,
- isadmin: authHeader.isAdminUser,
- campaign: campaign,
- ad: null
- };
- return bpad.findById(campaign.ad.id).then(ad => {
- data.ad = ad;
- return data;
- })
- .then((x) => {
- let stateMachine = Campaign.workflowStateMachineDefinition();
- return stateMachine.initialize(x.campaign._status.status, x.campaign, x);
- }).
- then(machine => {
- return machine.entity._status.status;
- });
- };
- Campaign.remoteMethod('getCurrentStatus', {
- description: 'Returns campaigns current status. Includes validation on flight period.',
- accepts: [
- { arg: 'campaign', type: 'object', required: true },
- { arg: 'options', 'type': 'object', 'http': 'optionsFromRequest' }
- ],
- returns: { arg: 'status', type: 'string' },
- http: { verb: 'put', path: '/getCurrentStatus' }
- });
- Campaign.getDays = (campaign) => {
- let campaignStartDate = moment(campaign.startDate);
- let campaignEndDate = moment(campaign.endDate);
- let today = moment();
- let passedDays = 0;
- let allDays = campaignEndDate.diff(campaignStartDate, 'days') + 1;
- if (today >= campaignStartDate.startOf('day')) {
- if (today <= moment(campaignEndDate).endOf('day')) {
- passedDays = today.diff(campaignStartDate, 'days') + 1;
- } else {
- passedDays = allDays;
- }
- }
- if (passedDays > allDays) {
- passedDays = allDays;
- }
- return {
- allDays: allDays,
- passedDays: passedDays
- };
- };
- Campaign.reach = (campaign: any, cb: any): void => {
- //if campaign is standard
- if (!campaign.isFixedInventory) {
- getReachForStandardCampaign(campaign, (error, result) => {
- if (error) {
- cb(error);
- }
- cb(null, result);
- });
- }
- else {
- //else if campaign is premium
- getReachForPremiumCampaign(campaign, (error, result) => {
- if (error) {
- cb(error);
- }
- cb(null, result);
- });
- }
- };
- const getReachForPremiumCampaign = (campaign: any, cb): any => {
- let flight = app.models.flight;
- let flightsCount = 0;
- let impressionsCount = 0;
- const passengerCapacity = 189;
- const loadFactor = 0.94;
- let reach = {
- flights: 0,
- impressions: 0
- };
- async.forEach(campaign.routes, (route, callback) => {
- let impressions = 0;
- flight.count({
- 'and': [
- {
- 'routeId': route.routeFrom + '-' + route.routeTo
- },
- {
- 'departureDate': { gte: (moment(campaign.startDate).format('YYYY-MM-DD') + 'T00:00:00.000Z') }
- },
- {
- 'departureDate': { lte: (moment(campaign.endDate).format('YYYY-MM-DD') + 'T00:00:00.000Z') }
- }
- ]
- },
- (error, count) => {
- if (error) {
- callback(error);
- }
- flightsCount += count;
- impressions = Math.round(count * passengerCapacity * route.inventory * loadFactor);
- impressionsCount += impressions;
- callback();
- });
- }, (err) => {
- if (err) {
- cb(err);
- }
- reach.flights = flightsCount;
- reach.impressions = Math.round(impressionsCount);
- cb(null, reach);
- });
- };
- const getReachForStandardCampaign = (campaign: any, cb): any => {
- let flight = app.models.flight;
- let allFlights = 0;
- let allImpressions = 0;
- const passengerCapacity = 189;
- const loadFactor = 0.94;
- let reach = {
- flights: 0,
- impressions: 0
- };
- //find all campaigns with fixed inventory
- Campaign.find(
- {
- where: {
- and: [
- { 'isFixedInventory': true },
- { '_status.status': { include: [CampaignStatus.Running, CampaignStatus.Stopped, CampaignStatus.Approved] } },
- ///{ or: [{ '_status.status': CampaignStatus.Approved }, { '_status.status': CampaignStatus.Running }, { '_status.status': CampaignStatus.Stopped }] },
- { 'startDate': { lte: moment.utc(campaign.startDate).format() } },
- { 'endDate': { gte: moment.utc(campaign.endDate).format() } }
- ]
- },
- fields: { 'routes': true }
- },
- (error, fixedInventoryCampaigns) => {
- //go through all selected routes in new campaign
- async.forEach(campaign.routes,
- (route, callback) => {
- let flights = 0;
- let impressions = 0;
- let inventory = 0;
- flight.count({
- 'and': [
- {
- 'routeId': route.routeFrom + '-' + route.routeTo
- },
- {
- 'departureDate': { gte: (moment(campaign.startDate).format('YYYY-MM-DD') + 'T00:00:00.000Z') }
- },
- {
- 'departureDate': { lte: (moment(campaign.endDate).format('YYYY-MM-DD') + 'T00:00:00.000Z') }
- }
- ]
- },
- (err, count) => {
- flights += count;
- //go through all premium campaigns to check if the selected route from new campaign is reserved
- fixedInventoryCampaigns.forEach(campaignObj => {
- //check all fixed inventory routes from one premium campaign
- campaignObj.routes.forEach(reservedRoute => {
- if (route.routeFrom === reservedRoute.routeFrom && route.routeTo === reservedRoute.routeTo) {
- inventory += reservedRoute.inventory;
- }
- });
- });
- if (inventory > 1) {
- inventory = 1; //max inventory can be 100%
- }
- //increase capacity by fixed inventory for this route
- impressions = flights * (passengerCapacity * (1 - inventory));
- allFlights += flights;
- allImpressions += impressions;
- callback(); //go to next selected route in new campaign
- });
- },
- (err) => {
- if (err) {
- cb(err);
- }
- reach.flights = allFlights;
- reach.impressions = Math.round(allImpressions * loadFactor);
- cb(null, reach);
- });
- });
- };
- Campaign.won = (id: string, boardingCard: any, amount: number, timestamp: any, cb) => {
- let boardingCardModel = app.models.boardingCard;
- waterfall([
- (next) => {
- winston.info('Wallet Charge find wallet started.');
- Campaign.findOne({ where: { id: id }, fields: { routes: false } }, (error, winningCampaign) => {
- Campaign.updateStatistics(winningCampaign, boardingCard, amount, timestamp);
- next(error, winningCampaign);
- });
- },
- (winningCampaign, next) => {
- winston.info('Wallet Charge create transaction started.');
- boardingCardModel.create({
- request: boardingCard,
- amount: amount,
- timestamp: timestamp,
- campaign: winningCampaign,
- campaignId: winningCampaign.id,
- advertiserId: winningCampaign.advertiserId
- },
- (error, transaction) => {
- next(error, winningCampaign, amount);
- });
- },
- (winningCampaign, winningAmount, next) => {
- winston.info('Wallet Charge update wallet started.');
- app.models.wallet.charge(winningCampaign, winningAmount, function (error: Error, results: any): void {
- next(error, 'completed');
- });
- }
- ],
- (error, result) => {
- if (error) {
- winston.error(`Won bid error`);
- cb(error);
- } else {
- winston.info('Won bid completed.');
- cb(null, result);
- }
- });
- };
- Campaign.observe('before save', (ctx, next) => {
- let valid = true;
- if (ctx.instance) {
- // validation on route inventory
- if (ctx.instance.isFixedInventory) {
- ctx.instance.routes.forEach(route => {
- let inventory = _.round(route.inventory * 100, 0);
- if (inventory < 1 || inventory > 100 || !Number.isInteger(inventory)) {
- let error = new Error('Validation error: Inventory should be a whole number and in range [1-100].');
- valid = false;
- return next(error);
- }
- });
- }
- }
- if (valid) {
- updateBudgetAndImpressionsLimits(ctx.instance).then(() => {
- if (ctx.isNewInstance) {
- if (!ctx.instance._status.status) {
- ctx.instance._status.status = CampaignStatus.Draft;
- ctx.instance._status.lastChangeDate = moment.utc();
- }
- return next();
- }
- if (!ctx.instance) {
- // not full update
- return next();
- }
- Campaign.findById(ctx.instance.id, { fields: { _status: true, statusLog: true } })
- .then((campaign) => {
- // preserve those fields
- ctx.instance._status = campaign._status;
- if (!ctx.instance.statusLog) {
- ctx.instance.statusLog = campaign.statusLog;
- }
- return next();
- }, (error) => {
- return next(error);
- });
- });
- }
- });
- const updateBudgetAndImpressionsLimits = (campaign: any): Promise => {
- return new Promise((resolve, reject) => {
- if (campaign && campaign.isFixedInventory) {
- let impressionsLimit = 0;
- getReachForPremiumCampaign(campaign, (err, reach) => {
- if (err) {
- return reject(new Error(err));
- }
- campaign.impressionsLimit = reach.impressions;
- campaign.totalBudget = reach.impressions * campaign.maxCPI;
- return resolve();
- });
- } else {
- return resolve();
- }
- });
- };
- function getRoutes(id: any, routes: any): Promise<any> {
- return new Promise((resolve, reject) => {
- if (!routes && id) {
- Campaign.findById(id, { fields: { routes: true } }, (error, results) => {
- if (error) {
- winston.error('Error occured while searching campaign routes.');
- reject(error);
- } else if (results.routes) {
- let routeIds = _.map(results.routes, 'routeId');
- resolve(routeIds);
- } else {
- winston.info('No matching routes find.');
- let e: { status?: number } = new Error('No matching routes find');
- e.status = 404;
- reject(e);
- }
- });
- } else {
- resolve(routes);
- }
- });
- }
- function getAggregationMatchingPublishers(routes: any): Promise<any> {
- return new Promise((resolve, reject) => {
- let publisherRouteCollection = app.models.publisherRoute.getDataSource().connector.collection(app.models.publisherRoute.modelName);
- publisherRouteCollection.aggregate([
- {
- $match: { routeId: { $in: routes } }
- },
- {
- $lookup: {
- from: 'publisher',
- localField: 'publisherId',
- foreignField: '_id',
- as: 'publisher'
- }
- },
- {
- $lookup: {
- from: 'route',
- localField: 'routeId',
- foreignField: '_id',
- as: 'route'
- }
- },
- {
- $unwind: '$publisher'
- },
- {
- $unwind: '$route'
- },
- {
- $facet:
- {
- routesPublishers: [
- {
- $group: {
- _id: '$routeId',
- origCode: { $first: '$route.origCode' },
- destCode: { $first: '$route.destCode' },
- publishers: {
- $push: {
- 'routeId': '$routeId',
- '_id': '$publisher._id',
- 'name': '$publisher.name'
- }
- }
- }
- }
- ],
- distinctPublishers: [
- {
- $group: {
- _id: '$publisherId',
- publisher: { $first: '$publisher' }
- }
- },
- {
- $project: {
- '_id': '$publisher._id',
- 'name': '$publisher.name'
- }
- }
- ]
- }
- }
- ], (err, data) => {
- if (err) {
- reject(err);
- } else {
- resolve(data);
- }
- });
- });
- }
- function getAggregationBlockedPublishers(advertiserId: any): Promise<any> {
- return new Promise((resolve, reject) => {
- let advertiserPublisherCollection = app.models.advertiserPublisher.getDataSource().connector.collection(app.models.advertiserPublisher.modelName);
- advertiserPublisherCollection.aggregate([
- {
- $match: {
- advertiserId: ObjectId(advertiserId),
- '_status.status': { $eq: 'blocked' }
- }
- },
- {
- $project: {
- '_id': '$publisherId'
- }
- }
- ], (err, data) => {
- if (err) {
- reject(err);
- } else {
- resolve(data);
- }
- });
- });
- }
- Campaign.matchingPublishers = (options: any, routes: any, campaignId: string, cb: any) => {
- async.waterfall([
- (callback) => {
- getRoutes(campaignId, routes)
- .then(checkRoutes => {
- callback(null, checkRoutes);
- return null;
- })
- .catch((err) => {
- winston.info('Error from >>getRoutes.');
- cb(err);
- });
- },
- (checkRoutes) => {
- async.parallel({
- matching: (callback) => {
- getAggregationMatchingPublishers(checkRoutes).then(response => {
- callback(null, response);
- })
- .catch((err) => {
- winston.info('Error from >>getRoutes.');
- cb(err);
- });
- },
- blocked: (callback) => {
- let authHeader = Utils.getRequestAuthHeaderSync(options);
- getAggregationBlockedPublishers(authHeader.user.advertiserId).then(response => {
- callback(null, response);
- })
- .catch((err) => {
- winston.info('Error from >>getRoutes.');
- cb(err);
- });
- }
- }, (err, results) => {
- if (err) {
- cb(err);
- } else {
- // results.matching[0].routesPublishers = _.reduce(results.matching[0].routesPublishers, function (obj: any, param: any): any {
- // obj[param._id] = param.publishers;
- // return obj;
- // }, {});
- results.blocked = _.map(results.blocked, '_id');
- cb(null, results);
- }
- });
- }
- ]);
- /*
- //Aggregation for getting distint not blocked publishers
- [
- { //Match routes
- $match: { routeId: { $in: ["STN-DUB","STN-BRI", "EMA-WMI"] } }
- },
- { //Get publishers of routes
- $lookup: {
- from: 'publisher',
- localField: 'publisherId',
- foreignField: '_id',
- as: 'publisher'
- }
- },
- { //Move publisher to upper level
- $unwind: '$publisher'
- },
- { //Add advertiserId variable while projecting result
- $project: {
- _id: 1,
- routeId: 1,
- publisher: 1,
- publisherId: 1,
- advertiserId: ObjectId('5a4f6acd8368b434dc4f633a')
- }
- },
- //Find advertiserPublishers
- //In version 3.6+ we could use pipeline in lookup
- //https://stackoverflow.com/questions/37086387/multiple-join-conditions-using-the-lookup-operator
- {
- $lookup: {
- from: 'advertiserPublisher',
- localField: 'advertiserId',
- foreignField: 'advertiserId',
- as: 'advertiserPublisher'
- }
- },
- { //We receive multiple advertiser publisher result, we expand list for next matching with publisherId
- $unwind: '$advertiserPublisher'
- },
- { //We create another projection with cond
- $project: {
- _id: 1,
- publisher: 1,
- routeId: 1,
- publisherId: 1,
- advertiserId: 1,
- advertiserPublisher: 1,
- isBlocked: {
- $and: [
- {$eq: ['$advertiserPublisher.publisherId', '$publisherId']},
- {$eq: ['$advertiserPublisher._status.status', 'blocked']}
- ]
- },
- publisherIdMatch: {
- $and: [
- {$eq: ['$advertiserPublisher.publisherId', '$publisherId']}
- ]
- }
- }
- },
- { //Return only matcing records
- $match: {
- 'isBlocked': false,
- 'publisherIdMatch': true
- }
- },
- {
- $group: {
- _id: '$publisherId',
- publisher: { $first: '$publisher' }
- }
- },
- {
- $project: {
- '_id': '$publisher._id',
- 'name': '$publisher.name'
- }
- }
- ]
- */
- };
- function getAggregationCampaignAds(campaignId: any): Promise<any> {
- return new Promise((resolve, reject) => {
- let campaignCollection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
- campaignCollection.aggregate([
- //Match campaignId
- {
- $match: { _id: ObjectId(campaignId) }
- },
- //Get all campaign publishers
- {
- $lookup: {
- from: 'campaignPublisher',
- localField: '_id',
- foreignField: 'campaignId',
- as: 'campaignPublisher'
- }
- },
- //Unwind campaign publishers
- {
- $unwind: '$campaignPublisher'
- },
- //Find ads of certain publisher
- //In version 3.6+ we could use pipeline in lookup
- //https://stackoverflow.com/questions/37086387/multiple-join-conditions-using-the-lookup-operator
- {
- $lookup: {
- from: 'adStatus',
- localField: 'bpadId',
- foreignField: 'bpadId',
- as: 'mainAd'
- }
- },
- {
- $unwind: '$mainAd'
- },
- {
- $unwind: '$mainAd._content'
- },
- {
- $project: {
- document: '$$ROOT',
- publisherIdMatch: {
- $and: [
- { $eq: ['$mainAd.publisherId', '$campaignPublisher.publisherId'] }
- ]
- }
- }
- },
- {
- $match: { 'publisherIdMatch': true }
- },
- {
- $group: {
- _id: '$$ROOT.document._id',
- campaign: { $first: '$$ROOT.document' },
- images: {
- //$push: '$$ROOT.document.mainAd'
- $push: {
- 'imageUrl': '$$ROOT.document.mainAd._content.imageUrl',
- 'language': '$$ROOT.document.mainAd._content.language',
- 'publisherId': '$$ROOT.document.mainAd.publisherId'
- }
- }
- }
- },
- {
- $project: {
- id: '$campaign._id',
- //title: '$campaign.title',
- //description: '$campaign.description',
- //targetEarly: '$campaign.targetEarly',
- //targetLate: '$campaign.targetLate',
- //statusLog: '$campaign.statusLog',
- //_status: '$campaign._status',
- startDate: '$campaign.startDate',
- endDate: '$campaign.endDate',
- maxCPI: '$campaign.maxCPI',
- totalBudget: '$campaign.totalBudget',
- impressionsLimit: '$campaign.impressionsLimit',
- maxDailyBudget: '$campaign.maxDailyBudget',
- baggageType: '$campaign.baggageType',
- fareClass: '$campaign.fareClass',
- ageFrom: '$campaign.ageFrom',
- ageTo: '$campaign.ageTo',
- gender: '$campaign.gender',
- routes: '$campaign.routes',
- dateCreated: '$campaign.dateCreated',
- dateModified: '$campaign.dateModified',
- isFixedInventory: '$campaign.isFixedInventory',
- advertiserId: '$campaign.advertiserId',
- channels: {
- pull: {
- image: '$images'
- }
- }
- }
- }
- ], (err, data) => {
- if (err) {
- reject(err);
- } else {
- resolve(data[0]);
- }
- });
- });
- }
- Campaign.observe('after save', (ctx, next) => {
- // getAggregationCampaignAds(ctx.instance.id).then(response => {
- // winston.info('CAMPAIGN DATA', JSON.stringify(response, null, 2));
- // })
- // .catch((err) => {
- // winston.info('Error while retrieving campaign information');
- // return next(err);
- // });
- let campaignData;
- if (ctx.isNewInstance) {
- Promise.join(
- createStatistics(ctx.instance, CampaignStatisticsPeriod.AllTime),
- /////ctx.instance.campaignStatistics.create({ avgCPI: 0, costs: 0, impressions: 0, routeStatistics: [] }),
- ////NOTE: update originFrom on bpad....
- app.models.bpad.updateAll({
- and: [
- { id: ObjectId(ctx.instance.ad ? ctx.instance.ad.id : null) },
- { '_status.status': AdStatus.Draft }
- ]
- },
- {
- originFrom: ctx.instance.id
- }),
- app.models.bpad.findById(ctx.instance.ad.id),
- // app.models.bpad.find({ where: {
- // campaigns: { inq: ctx.instance.id }
- // }})
- )
- .spread((campaignStatistics, ad, newAd) => {
- return adAppendCampaignId(newAd, ctx.instance.id);
- })
- .then(() => {
- return next();
- })
- .catch((error) => {
- winston.error(`Ad status can't be changed. Error stack trace: ${error}`);
- return next(error);
- });
- }
- else if (ctx.data) {
- next();
- }
- else {
- ///let campaignIdString = ctx.instance.id.toString();
- Promise.join(
- app.models.bpad.find({
- where: {
- advertiserId: ctx.instance.advertiserId,
- campaigns: ctx.instance.id,
- id: { neq: ctx.instance.ad.id }
- }
- }),
- app.models.bpad.findById(ctx.instance.ad.id),
- getAggregationCampaignAds(ctx.instance.id)
- )
- .spread((otherAds, ad, campaignDataAggregationResults) => {
- campaignData = campaignDataAggregationResults;
- ////let adId = ad.id.toString();
- let promiseArray = [];
- promiseArray.push(adAppendCampaignId(ad, ctx.instance.id));
- otherAds.forEach(element => {
- promiseArray.push(adRemoveCampaignId(element, ctx.instance.id));
- });
- return Promise.join(
- promiseArray
- // adRemoveCampaignId(oldAd, ctx.instance.id),
- // adAppendCampaignId(ad, ctx.instance.id)
- );
- })
- .then(() => {
- return app.models.bpad.updateAll({ eq: ['originFrom', ctx.instance.id] }, { originFrom: null })
- .then((updatedAll) => {
- return app.models.bpad.updateAll(
- { id: ctx.instance.ad.id, '_status.status': AdStatus.Draft },
- { originFrom: ctx.instance.id, '_status.status': AdStatus.Submitted }
- )
- .then((updatedInstance) => {
- let campaignLastChange = moment(ctx.instance.modified).milliseconds(0).toISOString();
- let statusLastChange = '';
- if (ctx.instance.statusLog && ctx.instance.statusLog.length > 0) {
- let lastStatusLog = _.last(ctx.instance.statusLog);
- statusLastChange = moment(lastStatusLog.lastChangeDate).milliseconds(0).toISOString();
- }
- 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)) {
- winston.info('Campaign Status Changed send notification started.', ctx.instance._status.status);
- return app.queue.CampaignStatusChanged.push({
- campaign: campaignData,
- status: campaignData._status.status
- }).then(() => {
- winston.info(`Campaign Status Changed send notification finished.`, campaignData._status.status);
- return next();
- });
- } else if (CampaignStatus.Running === ctx.instance._status.status) {
- winston.info('Campaign Changed send notification started for: ', ctx.instance.id);
- return app.queue.CampaignModify.push({
- campaign: campaignData
- }).then(() => {
- winston.info('Campaign Changed send notification finished for: ', ctx.instance.id);
- return next();
- });
- } else {
- return next();
- }
- });
- });
- })
- .catch((error) => {
- winston.error(`Ad status can't be changed. Error stack trace: ${error}`);
- return next(error);
- });
- }
- });
- function adAppendCampaignId(ad: any, campaignId: any): Promise {
- if (!ad) {
- return Promise.resolve();
- }
- let campaignIds = [];
- if (ad.campaigns) {
- campaignIds = ad.campaigns;
- }
- ////let campaignIdString = campaignId.toString();
- let indx = campaignIds.map(x => x.toString()).indexOf(campaignId.toString());
- /////_.findIndex(campaignIds, { id: campaignId.toString() });
- ////;
- if (indx < 0) {
- campaignIds.push(campaignId);
- let hasCampaign = campaignIds.length > 0;
- return ad.updateAttributes({ campaigns: campaignIds, used: hasCampaign });
- }
- return Promise.resolve();
- }
- function adRemoveCampaignId(ad: any, campaignId: any): Promise {
- if (!ad) {
- return Promise.resolve();
- }
- if (ad.campaigns) {
- ////let campaignIdString = campaignId.toString();
- let indx = ad.campaigns.map(x => x.toString()).indexOf(campaignId.toString()); ////ad.campaigns.indexOf(campaignId);
- ////_.findIndex(ad.campaigns, { id: campaignId.toString() });
- ////ad.campaigns.indexOf(campaignIdString);
- if (indx > -1) {
- ad.campaigns.splice(indx, 1);
- let hasCampaign = ad.campaigns.length > 0;
- return ad.updateAttributes({
- campaigns: ad.campaigns,
- used: hasCampaign
- });
- }
- }
- return Promise.resolve();
- }
- Campaign.actions = (campaignId, options, cb) => {
- let bpad = app.models.bpad;
- let data = prepareStateMachineData(options);
- initializeStateMachine(campaignId, data)
- .then(machine => {
- return machine.getActions(data);
- }).
- then(actions => {
- let actions_flat = [];
- if (actions !== undefined) {
- actions_flat = actions.map(element => element.name);
- }
- cb(null, actions_flat);
- return;
- })
- .catch((err) => {
- winston.info('Error from >>Get available actions.');
- cb(err);
- return;
- });
- };
- Campaign.remoteMethod('actions', {
- description: 'Get available actions for campaign.',
- accepts: [
- { arg: 'campaignId', type: 'string', required: true },
- { arg: 'options', 'type': 'object', 'http': 'optionsFromRequest' }
- ],
- returns: { arg: 'availableActions', type: 'array' },
- http: { verb: 'get', path: '/:campaignId/actions/' }
- });
- Campaign.executeAction = (campaignId: string, action: string, data: any, options: any) => {
- let bpad = app.models.bpad;
- // tslint:disable-next-line:no-console
- //console.log('__ bpad: ', bpad);
- let authHeader = Utils.getRequestAuthHeaderSync(options);
- let user = authHeader.user;
- let contextData = prepareStateMachineData(options);
- contextData.submitted = data;
- contextData.action = action;
- return initializeStateMachine(campaignId, contextData)
- .then(machine => {
- // tslint:disable-next-line:no-console
- //console.log('__ machine: ', machine);
- return machine.execute(action, contextData);
- })
- .then(x => {
- return x;
- })
- .catch((err) => {
- winston.error(`Error executing action ${action} on campaign ${campaignId}.`, { error: err, campaignId: campaignId, action: action, data: data, options: options });
- if (err && err.reason) {
- if (err.reason === WorkflowResults.ACTION_NOT_ALLOWED) {
- winston.info(`Action ${contextData.action} is not allowed.`);
- return Promise.reject(new BadRequest(BadRequest.CAMPAIGN_ACTION_NOT_ALLOWED));
- }
- return Promise.reject(new BadRequest(err.reason));
- }
- return Promise.reject(err);
- });
- };
- Campaign.remoteMethod('executeAction', {
- description: 'Executes actions on .',
- accepts: [
- { arg: 'campaignId', type: 'string', required: true },
- { arg: 'action', type: 'string', required: true },
- { arg: 'data', type: 'object', required: false },
- { arg: 'options', 'type': 'object', 'http': 'optionsFromRequest' }
- ],
- returns: { arg: 'campaign', type: 'object' },
- http: { verb: 'post', path: '/:campaignId/execute/:action/' }
- });
- const initializeStateMachine = (campaignId: string, contextData: any): Promise<StateMachine<any>> => {
- let bpad = app.models.bpad;
- return Campaign.findById(campaignId)
- .then((campaign) => {
- if (!campaign) {
- let error = new NotFound(NotFound.CAMPAIGN_NOT_FOUND);
- return Promise.reject(error);
- }
- if (contextData.action === 'approve') {
- app.models.adnexaUser.findOne({ where: { advertiserId: campaign.advertiserId } }, (error, advertiser) => {
- winston.info('Find advertiser finished.', advertiser);
- ejs.renderFile(path.resolve(__dirname, '../views/campaignApproved.ejs'),
- { fullName: advertiser.fullName, campaignTitle: campaign.title, campaignUrl: campaign.url, url: client.url + 'campaigns/' + campaign.id, img: client.url + 'assets/img/header.png' }, {}, (ejsError, html) => {
- Utils.sendMail({
- to: advertiser.email,
- subject: 'Advert approved for your campaign',
- html
- }, (err: Error) => {
- if (err) { return winston.info('> error sending campaign approved email'); }
- winston.info('> sending campaign approved email to:', advertiser.email);
- });
- });
- });
- }
- contextData.campaign = campaign;
- return initializeStateMachineByInstance(campaign, contextData);
- });
- };
- const initializeStateMachineByInstance = (campaign, contextData): Promise<StateMachine<any>> => {
- // let bpad = app.models.bpad;
- let adStatus = app.models.adStatus;
- let advertiser = app.models.advertiser;
- contextData.campaign = campaign;
- return adStatus.findById(campaign.ad.id).then((ad) => {
- contextData.ad = ad;
- return contextData;
- })
- .then((x) => {
- return advertiser.findById(campaign.advertiserId)
- .then(advr => {
- x.advertiser = advr;
- return x;
- });
- })
- .then((x) => {
- let stateMachine = Campaign.workflowStateMachineDefinition();
- return stateMachine.initialize(x.campaign._status.status, x.campaign, x);
- });
- };
- Campaign.changeStatus = (campaign, status, context): Promise<any> => {
- if (campaign._status.status === status) {
- return Promise.resolve(campaign);
- }
- let lastChangeDate = Date.now();
- let previous = campaign._status.status;
- let newStatus = {
- status: status,
- lastChangeDate: lastChangeDate
- };
- let statusLog = {
- status: status,
- previous: previous,
- lastChangeDate: lastChangeDate,
- userid: context.user.id
- };
- if (campaign.statusLog) {
- campaign.statusLog.push(statusLog);
- } else {
- campaign.statusLog = [statusLog];
- }
- return campaign.updateAttributes({
- _status: newStatus,
- statusLog: campaign.statusLog
- }).then(x => {
- return x;
- });
- };
- Campaign.workflowStateMachineDefinition = (): StateMachine<any> => {
- let blockAction = new StateAction<any>(
- {
- to: CampaignStatus.Blocked,
- name: CampaignActions.Block,
- conditions: [isAdminUser, isAdvertiserBlock]
- });
- let endAction = new StateAction<any>(
- {
- to: CampaignStatus.Ended,
- name: CampaignActions.End,
- auto: true,
- conditions: [
- advertiserNotBlocked,
- (entity, context) => { return Promise.resolve(entity.endDate < Date.now() && entity._status.status !== CampaignStatus.Ended); }],
- });
- return new StateMachine<any>(
- [
- new State<any>({
- name: CampaignStatus.Draft,
- actions: [
- endAction,
- blockAction,
- new StateAction<any>({
- name: CampaignActions.Edit,
- conditions: [advertiserNotBlocked, isAdvertiser]
- }),
- new StateAction<any>(
- {
- to: CampaignStatus.Running,
- name: CampaignActions.Run,
- conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved, haveFunds]
- }),
- new StateAction<any>(
- {
- to: CampaignStatus.Submitted,
- name: CampaignActions.Submit,
- action: submitCampaignAd,
- conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, isQuickAd, adNotRejectedOrBlocked]
- }),
- new StateAction<any>(
- {
- to: CampaignStatus.Running,
- name: CampaignActions.Run,
- conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved, haveFunds]
- }),
- new StateAction<any>(
- {
- to: CampaignStatus.Submitted,
- name: CampaignActions.Submit,
- action: submitCampaignAd,
- conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, isQuickAd]
- }),
- ////NOTE: premium advertiser submit action.
- new StateAction<any>(
- {
- to: CampaignStatus.Submitted,
- name: CampaignActions.Submit,
- action: submitCampaignAd,
- conditions: [advertiserNotBlocked, isPremiumAdvertiser, adNotRejectedOrBlocked]
- }),
- ]
- }),
- new State<any>({
- name: CampaignStatus.Submitted,
- actions: [
- endAction,
- blockAction,
- new StateAction<any>(
- {
- to: CampaignStatus.Draft,
- name: CampaignActions.EditRequested,
- conditions: [advertiserNotBlocked, isAdvertiser]
- }),
- new StateAction<any>(
- {
- to: CampaignStatus.Running,
- name: CampaignActions.Run,
- conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved, haveFunds]
- }),
- new StateAction<any>(
- {
- to: CampaignStatus.Draft,
- name: CampaignActions.EditRequested,
- conditions: [advertiserNotBlocked, isAdvertiser]
- }),
- new StateAction<any>(
- {
- to: CampaignStatus.Running,
- name: CampaignActions.Run,
- conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved]
- }),
- new StateAction<any>(
- {
- to: CampaignStatus.Approved,
- name: CampaignActions.Approve,
- action: approveCampaignAd,
- conditions: [advertiserNotBlocked, isAdminUser, isPremiumAdvertiser, adNotRejectedOrBlocked],
- }),
- ////NOTE: Quick AD approval...
- new StateAction<any>(
- {
- to: CampaignStatus.Approved,
- name: CampaignActions.Approve,
- action: approveCampaignAd,
- conditions: [advertiserNotBlocked, notPremiumAdvertiser, isAdminUser, adNotRejectedOrBlocked],
- }),
- new StateAction<any>(
- {
- to: CampaignStatus.Rejected,
- name: CampaignActions.Reject,
- conditions: [advertiserNotBlocked, isAdminUser],
- }),
- ]
- }),
- new State<any>({
- name: CampaignStatus.Approved,
- actions: [
- endAction,
- blockAction,
- new StateAction<any>(
- {
- to: CampaignStatus.Running,
- name: CampaignActions.Run,
- conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved, haveFunds]
- }),
- new StateAction<any>(
- {
- to: CampaignStatus.Running,
- name: CampaignActions.Run,
- conditions: [advertiserNotBlocked, isAdvertiser, isPremiumAdvertiser, adApproved]
- }),
- new StateAction<any>(
- {
- to: CampaignStatus.Draft,
- name: CampaignActions.EditRequested,
- conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser]
- })
- ]
- }),
- new State<any>({
- name: CampaignStatus.Running,
- actions: [
- endAction,
- blockAction,
- new StateAction<any>(
- {
- name: CampaignActions.Edit,
- conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved]
- }),
- new StateAction<any>({
- to: CampaignStatus.Stopped,
- name: CampaignActions.Stop,
- conditions: [advertiserNotBlocked, isAdvertiser]
- }),
- ]
- }),
- new State<any>({
- name: CampaignStatus.Stopped,
- actions: [
- endAction,
- blockAction,
- new StateAction<any>(
- {
- name: CampaignActions.Edit,
- conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved]
- }),
- new StateAction<any>(
- {
- to: CampaignStatus.Running,
- name: CampaignActions.Run,
- conditions: [advertiserNotBlocked, adApproved, isAdvertiser, haveFunds]
- })
- ]
- }),
- new State<any>({
- name: CampaignStatus.SystemStopped,
- actions: [
- endAction,
- blockAction,
- new StateAction<any>(
- {
- to: CampaignStatus.Stopped,
- name: CampaignActions.EditDone,
- conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved]
- }),
- new StateAction<any>(
- {
- name: CampaignActions.Edit,
- conditions: [advertiserNotBlocked, isAdvertiser, notPremiumAdvertiser, adApproved]
- })
- ]
- }),
- new State<any>({
- name: CampaignStatus.Rejected,
- actions: [
- endAction,
- blockAction,
- new StateAction<any>(
- {
- to: CampaignStatus.Draft,
- name: CampaignActions.EditRequested,
- conditions: [advertiserNotBlocked, isAdvertiser]
- })
- ]
- }),
- new State<any>({
- name: CampaignStatus.Blocked,
- actions: [
- new StateAction<any>(
- {
- to: previousState,
- name: CampaignActions.UnBlock,
- conditions: [isAdminUser]
- })
- ]
- }),
- new State<any>({
- name: CampaignStatus.Ended,
- actions: []
- }),
- ],
- (entity, status, context) => {
- return Campaign.changeStatus(entity, status, context);
- });
- };
- const isAdvertiserBlock = (entity: any, context: any): Promise<boolean> => {
- if (context) {
- return Promise.resolve(context.isAdvertiserBlock === true);
- }
- return Promise.resolve(false);
- };
- const adApproved = (entity: any, context: any): Promise<boolean> => {
- return Promise.resolve(context !== undefined &&
- context.ad !== undefined &&
- context.ad._status.status === AdStatus.Approved
- );
- };
- const adNotRejectedOrBlocked = (entity: any, context: any): Promise<boolean> => {
- return Promise.resolve(context !== undefined &&
- context.ad !== undefined &&
- context.ad._status.status !== AdStatus.Rejected &&
- context.ad._status.status !== AdStatus.Blocked
- );
- };
- const adSubmitted = (entity: any, context: any): Promise<boolean> => {
- return Promise.resolve(context.ad._status.status === AdStatus.Submitted);
- };
- const adNew = (entity: any, context: any): Promise<boolean> => {
- return Promise.resolve(context.ad._status.status === AdStatus.Draft);
- };
- const isQuickAd = (entity: any, context: any): Promise<boolean> => {
- return Promise.resolve(context !== undefined &&
- context.ad !== undefined &&
- context.ad.originFrom !== undefined && context.ad.originFrom !== '' &&
- context.ad._status.status !== AdStatus.Approved
- );
- };
- const notPremiumAdvertiser = (entity: any, context: any): Promise<boolean> => {
- return Promise.resolve(entity.isFixedInventory !== true);
- };
- const isPremiumAdvertiser = (entity: any, context: any): Promise<boolean> => {
- return Promise.resolve(entity.isFixedInventory === true);
- };
- const isAdminUser = (entity: any, context: any): Promise<boolean> => {
- return Promise.resolve(context !== undefined && context.isadmin === true);
- };
- const isAdvertiser = (entity: any, context: any): Promise<boolean> => {
- return Promise.resolve(
- context &&
- context.user &&
- context.user.advertiserId.toString() === entity.advertiserId.toString());
- };
- const advertiserNotBlocked = (entity: any, context: any): Promise<boolean> => {
- if (context &&
- context.advertiser &&
- context.advertiser.blocked === true) {
- return Promise.resolve(false);
- }
- return Promise.resolve(true);
- };
- const haveFunds = (entity: any, context: any): Promise<boolean> => {
- return app.models.wallet.findOne({ where: { advertiserId: context.user.advertiserId } })
- .then((wallet) => {
- if (!wallet) {
- return Promise.resolve(false);
- }
- return Promise.resolve(wallet.balance > 0);
- });
- };
- const previousState = (entity: any, context: any): Promise<string> => {
- if (entity.statusLog) {
- let lastStatusLog = _.last(entity.statusLog);
- if (lastStatusLog) {
- return lastStatusLog.previous;
- }
- }
- return '';
- };
- const approveCampaignAd = (entity: any, context: any): Promise<any> => {
- ////NOTE: update AD to approved... Approve AD if campaign is approved
- if (context.ad._status.status === AdStatus.Submitted || context.ad._status.status === AdStatus.Rejected) {
- return app.models.bpad.executeAction(entity.ad.id, AdActions.Approve, null, context.options)
- .then(x => {
- return updateCampaingAd(entity);
- });
- }
- return Promise.resolve(entity);
- };
- const rejectCampaignAd = (entity: any, context: any): Promise<any> => {
- ////NOTE: update AD to approved...
- if (context.ad._status.status === AdStatus.Submitted) {
- return app.models.bpad.executeAction(entity.ad.id, AdActions.Reject, null, context.options)
- .then(x => {
- return updateCampaingAd(entity);
- });
- }
- return Promise.resolve(entity);
- };
- const submitCampaignAd = (entity: any, context: any): Promise<any> => {
- if (context.ad._status.status === AdStatus.Draft) {
- //////// AD state machine
- ////(id: string, action: string, data: any, options: any)
- return app.models.bpad.executeAction(entity.ad.id, AdActions.Submit, null, context.options)
- .then(x => {
- return updateCampaingAd(entity);
- });
- }
- return Promise.resolve(entity);
- };
- const prepareStateMachineData = (options: any): any => {
- let authHeader = Utils.getRequestAuthHeaderSync(options);
- let contextData = {
- options: options,
- user: authHeader.user,
- roles: authHeader.roles,
- isadmin: authHeader.isAdminUser,
- isAdvertiserBlock: options.isAdvertiserBlock,
- submitted: null,
- campaign: null,
- ad: null
- };
- return contextData;
- };
- const updateCampaingAd = (campaign: any): Promise<any> => {
- return app.models.bpad.findById(campaign.ad.id)
- .then(newAd => {
- return campaign.updateAttributes({ ad: newAd }).then((err, instance) => {
- // if (err) {
- // return Promise.reject(err);
- // }
- return instance;
- });
- });
- };
- Campaign.exhausted = (campaignId: string): Promise<void> => {
- // tslint:disable-next-line:no-console
- console.log('__ Campaign.exhausted -- campaignId: ', campaignId);
- // let cmpId = JSON.parse(campaignId).id;
- return new Promise<void>((resolve, reject) => {
- winston.info(`Campaign ${campaignId} is exhausted.`, { campaignId: campaignId });
- waterfall([
- (next) => {
- // app.models.campaign.findOne({where:{_id: campaignId}}, (error, campaign) => {
- app.models.campaign.findOne({ where: { id: campaignId } }, (error, campaign) => {
- if (!error && !campaign) {
- error = new Error(`Campaign ${campaignId} not found for campaign exhausted event.`);
- }
- winston.info('Find campaign finished.', error, campaign);
- next(error, campaign);
- });
- },
- (campaign, next) => {
- app.models.advertiser.findOne({ where: { id: campaign.advertiserId } }, (error, advertiser) => {
- winston.info('__ campaign.exausted -- find advertiser ', advertiser);
- if (!error && !advertiser) {
- error = new Error(`Advertiser ${campaign.advertiserId} not found for campaign exhausted event.`);
- }
- next(error, campaign, advertiser);
- });
- },
- (campaign, advertiser, next) => {
- winston.info('Find advertiser-user started.');
- app.models.adnexaUser.findOne({ where: { advertiserId: advertiser.id } }, (error, user) => {
- winston.info('Find advertiser-user finished.', user ? user.email : null);
- next(error, campaign, advertiser, user);
- });
- },
- (campaign, advertiser, user, next) => {
- if (campaign.isFixedInventory) {
- Campaign.changeStatus(campaign, CampaignStatus.Ended, { user: { id: 'admin' } });
- } else {
- Campaign.changeStatus(campaign, CampaignStatus.SystemStopped, { user: { id: 'admin' } });
- }
- next(null, campaign, advertiser, user);
- },
- (campaign, advertiser, user, next) => {
- if (user) {
- ejs.renderFile(path.resolve(__dirname, '../views/campaignExhausted.ejs'),
- { fullName: user.fullName, title: campaign.title, url: client.url + 'campaigns/' + campaign.id, img: client.url + 'assets/img/header.png' }, {}, (error, html) => {
- Utils.sendMail({
- to: user.email,
- subject: 'Your campaign has been stopped',
- html
- }, (err: Error) => {
- if (err) { return winston.info('> error sending campaign exhausted email'); }
- winston.info('> sending campaign exhausted email to:', user.email);
- next(null);
- });
- });
- } else {
- next(null);
- }
- }
- ],
- (error, result) => {
- if (error) {
- winston.error(`Error handling the campaign exhausted event for campaing ${campaignId}`, error);
- reject(error);
- } else {
- resolve();
- }
- });
- });
- };
- Campaign.exhaustedSoon = (campaignId: string): Promise<void> => {
- return new Promise<void>((resolve, reject) => {
- waterfall([
- (next) => {
- winston.info('Find campaign started.', campaignId);
- app.models.campaign.findOne({ where: { _id: campaignId } }, (error, campaign) => {
- winston.info('Find campaign finished.', campaign);
- next(error, campaign);
- });
- },
- (campaign, next) => {
- winston.info('Find advertiser started.');
- app.models.advertiser.findOne({ where: { _id: campaign.advertiserId } }, (error, advertiser) => {
- winston.info('Find advertiser finished.', advertiser);
- next(error, campaign, advertiser);
- });
- },
- (campaign, advertiser, next) => {
- winston.info('Find advertiser-user started.');
- app.models.adnexaUser.findOne({ where: { advertiserId: advertiser.id } }, (error, user) => {
- winston.info('Find advertiser-user finished.', user ? user.email : null);
- next(error, campaign, advertiser, user);
- });
- },
- (campaign, advertiser, user, next) => {
- if (user) {
- ejs.renderFile(path.resolve(__dirname, '../views/campaignExhaustedSoon.ejs'),
- { fullName: user.fullName, title: campaign.title, url: client.url + 'campaigns/' + campaign.id, img: client.url + 'assets/img/header.png' }, {}, (error, html) => {
- if (error) {
- winston.error(error);
- }
- Utils.sendMail({
- to: user.email,
- subject: 'Your Budget or Impression limit has nearly been reached',
- html
- }, (err: Error) => {
- if (err) { return winston.info('> error sending campaign exhausted soon email'); }
- winston.info('> sending campaign exhausted soon to:', user.email);
- next(null);
- });
- });
- } else {
- next(null);
- }
- }
- ],
- (error, result) => {
- if (error) {
- winston.error(error);
- reject(error);
- } else {
- resolve();
- }
- });
- });
- };
- Campaign.getTopCPICampaigns = (num: number, options: any, cb: any) => {
- let authObj = Utils.getRequestAuthHeaderSync(options);
- let advertiserId;
- if (authObj.isAdminUser) {
- advertiserId = null;
- } else {
- advertiserId = authObj.user.advertiserId;
- }
- let collection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
- let topCPIQuery = TopCampaignsQuery(advertiserId, num, ['running'], 'CPI');
- collection.aggregate(topCPIQuery.query, (error, campaigns) => {
- if (error) {
- cb(error);
- }
- cb(null, campaigns);
- });
- };
- Campaign.getTopImpressionsCampaigns = (num: number, options: any, cb: any) => {
- let authObj = Utils.getRequestAuthHeaderSync(options);
- let advertiserId;
- if (authObj.isAdminUser) {
- advertiserId = null;
- } else {
- advertiserId = authObj.user.advertiserId;
- }
- let collection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
- let topCPIQuery = TopCampaignsQuery(advertiserId, num, ['running'], 'IMPRESSIONS');
- collection.aggregate(topCPIQuery.query, (error, campaigns) => {
- if (error) {
- cb(error);
- }
- cb(null, campaigns);
- });
- };
- Campaign.getTopCPIRoutes = (num: number, options: any, cb: any) => {
- let authObj = Utils.getRequestAuthHeaderSync(options);
- let advertiserId;
- if (authObj.isAdminUser) {
- advertiserId = null;
- } else {
- advertiserId = authObj.user.advertiserId;
- }
- let collection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
- let topCPIRouteQuery = TopRoutesQuery(advertiserId, num, ['running'], 'CPI');
- collection.aggregate(topCPIRouteQuery.query, (error, routes) => {
- if (error) {
- cb(error);
- }
- cb(null, routes);
- });
- };
- Campaign.getTopImpressionsRoutes = (num: number, options: any, cb: any) => {
- let authObj = Utils.getRequestAuthHeaderSync(options);
- let advertiserId;
- if (authObj.isAdminUser) {
- advertiserId = null;
- } else {
- advertiserId = authObj.user.advertiserId;
- }
- let collection = app.models.campaign.getDataSource().connector.collection(app.models.campaign.modelName);
- let topCPIRouteQuery = TopRoutesQuery(advertiserId, num, ['running'], 'IMPRESSIONS');
- collection.aggregate(topCPIRouteQuery.query, (error, routes) => {
- if (error) {
- cb(error);
- }
- cb(null, routes);
- });
- };
- const prepareStateMachineContext = (options: any): any => {
- let authHeader = Utils.getRequestAuthHeaderSync(options);
- let data = {
- user: authHeader.user,
- isAdminUser: authHeader.isAdminUser,
- isAdvertiserBlock: options.isAdvertiserBlock,
- roles: authHeader.roles,
- ad: null
- };
- return data;
- };
- Campaign.advertiserHasCampaign = (fixedInventory: boolean, advertiserId: string, options: any, cb: any) => {
- Campaign.count({
- advertiserId: advertiserId,
- isFixedInventory: fixedInventory
- }, (error, count) => {
- if (error) {
- cb(error);
- }
- if (count > 0) {
- cb(null, true);
- }
- else {
- cb(null, false);
- }
- });
- };
- // 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.
- const remoteMethod = new RemoteMethod(Campaign,
- [
- 'reach',
- 'getTopImpressionsRoutes',
- 'getTopImpressionsCampaigns',
- 'getTopCPIRoutes',
- 'getTopCPICampaigns',
- 'getStatisticsSummary',
- 'getCurrentStatus',
- 'getCountByStatus',
- 'findOne',
- 'find',
- 'findById',
- //'won',
- 'getImpressionsSummary',
- 'getCampaignStatisticsTotal',
- 'advertiserHasCampaign',
- 'executeAction',
- 'actions',
- 'create',
- 'patchAttributes',
- 'replaceById',
- 'matchingPublishers'
- ]);
- remoteMethod.disableMethods();
- };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement