@mediarithmics/plugins-nodejs-sdk
Version:
This is the mediarithmics nodejs to help plugin developers bootstrapping their plugin without having to deal with most of the plugin boilerplate
210 lines (186 loc) • 6.2 kB
text/typescript
import * as express from "express";
import * as _ from "lodash";
import * as cache from "memory-cache";
import {
BasePlugin,
PluginProperty,
BidOptimizerBaseInstanceContext,
BidOptimizer,
BidOptimizerRequest,
BidOptimizerPluginResponse,
SaleCondition
} from "../../../index";
export abstract class BidOptimizerPlugin extends BasePlugin {
instanceContext: Promise<BidOptimizerBaseInstanceContext>;
/**
*
* @param bidOptimizerId
*/
async fetchBidOptimizer(bidOptimizerId: string): Promise<BidOptimizer> {
const bidOptimizerResponse = await super.requestGatewayHelper(
"GET",
`${this.outboundPlatformUrl}/v1/bid_optimizers/${bidOptimizerId}`
);
this.logger.debug(
`Fetched Bid Optimizer: ${bidOptimizerId} - ${JSON.stringify(
bidOptimizerResponse.data
)}`
);
return bidOptimizerResponse.data;
}
/**
*
* @param bidOptimizerId
*/
async fetchBidOptimizerProperties(
bidOptimizerId: string
): Promise<PluginProperty[]> {
const bidOptimizerPropertyResponse = await super.requestGatewayHelper(
"GET",
`${this.outboundPlatformUrl}/v1/bid_optimizers/${
bidOptimizerId
}/properties`
);
this.logger.debug(
`Fetched Creative Properties: ${bidOptimizerId} - ${JSON.stringify(
bidOptimizerPropertyResponse.data
)}`
);
return bidOptimizerPropertyResponse.data;
}
findBestSalesConditions(
bidPrice: number,
salesConditions: SaleCondition[]
): SaleCondition {
// Optimization, we only do the stringify if we are really on debug / silly mode
if (this.logger.level === "debug" || this.logger.level === "silly") {
this.logger.debug(
`Looking to find the best sale condition for CPM: ${
bidPrice
} in: ${JSON.stringify(salesConditions, null, 4)}`
);
}
const eligibleSalesConditions = salesConditions.filter(sc => {
return sc.floor_price <= bidPrice;
});
// Optimization, we only do the stringify if we are really on debug / silly mode
if (this.logger.level === "debug" || this.logger.level === "silly") {
this.logger.debug(
`Found eligible sales condition for CPM: ${
bidPrice
} in: ${JSON.stringify(eligibleSalesConditions, null, 4)}`
);
}
const sortedEligibleSalesConditions = eligibleSalesConditions.sort(
(a, b) => {
return a.floor_price - b.floor_price;
}
);
// Optimization, we only do the stringify if we are really on debug / silly mode
if (this.logger.level === "debug" || this.logger.level === "silly") {
this.logger.debug(
`Sorted eligible sales condition for CPM: ${
bidPrice
} in: ${JSON.stringify(sortedEligibleSalesConditions, null, 4)}`
);
}
return sortedEligibleSalesConditions[0];
}
/**
* Method to build an instance context
* To be overriden to get a cutom behavior
* This is a default provided implementation
* @param bidOptimizerId
*/
protected async instanceContextBuilder(
bidOptimizerId: string
): Promise<BidOptimizerBaseInstanceContext> {
const bidOptimizerP = this.fetchBidOptimizer(bidOptimizerId);
const bidOptimizerPropsP = this.fetchBidOptimizerProperties(bidOptimizerId);
const results = await Promise.all([bidOptimizerP, bidOptimizerPropsP]);
const bidOptimizer = results[0];
const bidOptimizerProps = results[1];
const context = {
bidOptimizer: bidOptimizer,
bidOptimizerProperties: bidOptimizerProps
};
return context;
}
/**
*
* @param request
* @param instanceContext
*/
protected abstract onBidDecisions(
request: BidOptimizerRequest,
instanceContext: BidOptimizerBaseInstanceContext
): Promise<BidOptimizerPluginResponse>;
private initBidDecisions(): void {
this.app.post(
"/v1/bid_decisions",
this.asyncMiddleware(
async (req: express.Request, res: express.Response) => {
if (!req.body || _.isEmpty(req.body)) {
const msg = {
error: "Missing request body"
};
this.logger.error(
"POST /v1/bid_decisions : %s",
JSON.stringify(msg)
);
return res.status(500).json(msg);
} else {
if (
this.logger.level === "debug" ||
this.logger.level === "silly"
) {
this.logger.debug(
`POST /v1/bid_decisions ${JSON.stringify(req.body)}`
);
}
const bidOptimizerRequest = req.body as BidOptimizerRequest;
if (!this.onBidDecisions) {
const errMsg = "No BidOptimizer listener registered!";
this.logger.error(errMsg);
return res.status(500).json({ error: errMsg });
}
if (
!this.pluginCache.get(
bidOptimizerRequest.campaign_info.bid_optimizer_id
)
) {
this.pluginCache.put(
bidOptimizerRequest.campaign_info.bid_optimizer_id,
this.instanceContextBuilder(
bidOptimizerRequest.campaign_info.bid_optimizer_id
),
this.INSTANCE_CONTEXT_CACHE_EXPIRATION
);
} // We init the specific route to listen for bid decisions requests
const instanceContext = await this.pluginCache.get(
bidOptimizerRequest.campaign_info.bid_optimizer_id
);
const bidOptimizerResponse = await this.onBidDecisions(
bidOptimizerRequest,
instanceContext
);
if (
this.logger.level === "debug" ||
this.logger.level === "silly"
) {
this.logger.debug(
`Returning: ${JSON.stringify(bidOptimizerResponse)}`
);
}
return res.status(200).send(JSON.stringify(bidOptimizerResponse));
}
}
)
);
}
constructor() {
super();
this.initBidDecisions();
this.setErrorHandler();
}
}