/**
* @module mongodb/models/publisher
*/
var mongoose = require('mongoose');
var deepPopulate = require('mongoose-deep-populate');
var Schema = mongoose.Schema;
var util = require('util');
var _ = require('underscore');
var mongooseApiQuery = require('mongoose-api-query');
var creativeSchema = require('./advertiser').creativeSchema;
var TreeDocument = require('./lib/treeDocument').TreeDocument;
// ---- Publisher Collection Models ---- //
var HostedCreative = mongoose.model('HostedCreative', creativeSchema);
/**
* Mongoose Schema representing Publisher Placements
*
* @class
* @type {Schema}
*/
var placementSchema = new Schema({
name: String,
active: { type: Boolean, default: false },
description: String,
tstamp: { type: Date, default: Date.now },
h: { type: Number, required: true },
w: { type: Number, required: true },
pos: { type: Number, required: true, max: 7 }, //see OpenRTB 2.3 section 5.4 for list & definitions
defaultType: { type: String, required: true, enum: ['passback','hostedCreative','psa','hide'], default: 'hide' },
passbackTag: { type: String, required: false },
hostedCreatives: [creativeSchema]
});
placementSchema.plugin(deepPopulate, {});
placementSchema.plugin(mongooseApiQuery);
/**
* Hook to apply logic on hostedCreative selection when serving up default creatives.
*
* Just chooses a random creative right now.
* @returns {*}
*/
placementSchema.methods.getRandomHostedCreative = function(){
if (this.hostedCreatives){
return this.hostedCreatives[Math.floor(Math.random()*this.hostedCreatives.length)]
}
};
/**
* Cross validation to ensure that proper data has been provided with individual defaultType options
*/
placementSchema.pre('save', function(next){
var err = null;
switch (this.defaultType){
case "passback":
if (!this.passbackTag){
err = new Error("defaultType set to 'passback' but no passbackTag was provided");
}
break;
case "hostedCreative":
if (this.hostedCreatives && this.hostedCreatives.length > 0){
break;
} else {
err = new Error("defaultType set to 'hostedCreative' but no hostedCreatives provided");
}
//TODO: add case for PSA when that functionality gets built out
}
return next(err);
});
var Placement = mongoose.model('Placement',placementSchema);
/**
* Mongoose Schema representing Publisher Pages
*
* @class
* @type {Schema}
*/
var pageSchema = new Schema({
name: String,
active: { type: Boolean, default: false },
description: String,
tstamp: { type: Date, default: Date.now },
url: { type: String, required: true },
placements: [placementSchema],
clique: { type: String, required: false, ref: 'Clique' }//TODO: Add validators to check string against Clique._id
});
pageSchema.plugin(deepPopulate, {});
pageSchema.plugin(mongooseApiQuery);
var Page = mongoose.model('Page', pageSchema);
/**
* Mongoose Schema representing Publisher Sites
*
* @class
* @type {Schema}
*/
var siteSchema = new Schema({
name: String,
active: { type: Boolean, default: false },
description: String,
logo_url: String,
tstamp: { type: Date, default: Date.now },
domain_name: { type: String, required: true },
pages: [pageSchema],
blacklist: [{type: String}],
bidfloor: Number,
clique: { type: String, required: true, ref: 'Clique' }//TODO: Add validators to check string against Clique._id
});
// Virtual field to retrieve secure URL
siteSchema.virtual('logo_secure_url').get(function(){
if (this.logo_url){
return this.logo_url.replace('http://', 'https://');
}
});
siteSchema.set('toObject', { virtuals: true });
siteSchema.set('toJSON', { virtuals: true });
siteSchema.plugin(deepPopulate, {});
siteSchema.plugin(mongooseApiQuery);
//pre-save hook to populate logo_url with parent logo_url if not provided
siteSchema.pre('save', function(next){
if (!this.logo_url){
// this.parent call doesn't work here for some reason
this.logo_url = this.__parent.logo_url;
}
next();
});
var Site = mongoose.model('Site', siteSchema);
/**
* Mongoose Schema representing Publishers
*
* @class
* @type {Schema}
*/
var publisherSchema = new Schema({
name: { type: String, required: true, index: true },
user: [{ type: Schema.ObjectId, ref: 'User' }],
organization: { type: Schema.ObjectId, ref: 'Organization' },
logo_url: String,
revenue_share: { type: Number, required: true, default: 0.10 },
website: String,
description: String,
tstamp: { type: Date, default: Date.now },
sites: [siteSchema],
cliques: [{ type: String, required: true, ref: 'Clique' }] //TODO: Add validators to check string against Clique._id
});
// Virtual field to retrieve secure URL
publisherSchema.virtual('logo_secure_url').get(function(){
if (this.logo_url){
return this.logo_url.replace('http://', 'https://');
}
});
publisherSchema.set('toObject', { virtuals: true });
publisherSchema.set('toJSON', { virtuals: true });
publisherSchema.plugin(deepPopulate, {});
publisherSchema.plugin(mongooseApiQuery);
publisherSchema.set('autoIndex', false); // performance gains
var Publisher = mongoose.model('Publisher', publisherSchema);
/**
* [TreeDocument]{@link mongodb/models/lib/treeDocument~TreeDocument} subclass wrapping Publisher document tree.
*
* @param {mongoose.connection} connection DB connection object
* @param {Object} options
* @constructor
*/
var PublisherModels = function(connection, options){
options = options || {};
this.connection = connection;
// Publisher models
this.Publisher = this.connection.model('Publisher');
this.Site = this.connection.model('Site');
this.Page = this.connection.model('Page');
this.Placement = this.connection.model('Placement');
this.HostedCreative = this.connection.model('HostedCreative');
var branches = [['Site','Page','Placement','HostedCreative']];
var top_level_node = 'Publisher';
TreeDocument.call(this, top_level_node, branches, options);
};
util.inherits(PublisherModels, TreeDocument);
/**
* Gets placement document nested several levels deep in publisher document hierarchy.
*
* I'm not proud of this query function, probably could be done more efficiently
* but I can't figure out how.
*
* @param placement_id ObjectID of desired placement
* @param read set to "secondary" if reading from slave
* @param callback callback function
*/
PublisherModels.prototype.getPlacementFromID = function(placement_id, read, callback){
this.getNestedObjectById(placement_id, 'Placement', callback);
};
exports.PublisherModels = PublisherModels;