├── lib ├── before │ ├── 0-lodash.js │ ├── helper.js │ └── constants.js └── schemas │ ├── clicks.js │ ├── impressions.js │ ├── areas.js │ ├── cities.js │ ├── categories.js │ ├── time-logs.js │ ├── channels.js │ ├── contents.js │ ├── images.js │ ├── 0-shared-schemas.js │ └── campaigns.js └── README.md /lib/before/0-lodash.js: -------------------------------------------------------------------------------- 1 | _ = lodash 2 | -------------------------------------------------------------------------------- /lib/before/helper.js: -------------------------------------------------------------------------------- 1 | Helper = {} 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Load shared schemas before anything else in order to share across gnomad-ipad and gnomad-web application 2 | -------------------------------------------------------------------------------- /lib/schemas/clicks.js: -------------------------------------------------------------------------------- 1 | var ClickSchema = new SimpleSchema([SharedSchemas.CreatedAtSchema, { 2 | driverId: { 3 | type: String 4 | }, 5 | campaignId: { 6 | type: String, 7 | optional: true 8 | } 9 | }]) 10 | 11 | Clicks = new Mongo.Collection('clicks') 12 | Clicks.attachSchema(ClickSchema) 13 | -------------------------------------------------------------------------------- /lib/schemas/impressions.js: -------------------------------------------------------------------------------- 1 | var ImpressionSchema = new SimpleSchema([SharedSchemas.CreatedAtSchema, { 2 | driverId: { 3 | type: String 4 | }, 5 | campaignId: { 6 | type: String 7 | }, 8 | }]) 9 | 10 | Impressions = new Mongo.Collection('impressions') 11 | Impressions.attachSchema(ImpressionSchema) 12 | -------------------------------------------------------------------------------- /lib/schemas/areas.js: -------------------------------------------------------------------------------- 1 | var AreaSchema = new SimpleSchema([ 2 | SharedSchemas.LocationSchema, 3 | { 4 | cityId: { 5 | type: String, 6 | } 7 | } 8 | ]) 9 | 10 | Areas = new Mongo.Collection('areas') 11 | Areas.attachSchema(AreaSchema) 12 | 13 | if(Meteor.isServer) { 14 | Areas._ensureIndex({geolocation: "2dsphere"}) 15 | } 16 | -------------------------------------------------------------------------------- /lib/schemas/cities.js: -------------------------------------------------------------------------------- 1 | var CitySchema = new SimpleSchema([ 2 | SharedSchemas.LocationSchema, 3 | { 4 | searchName: { 5 | label: 'State and City', 6 | type: String 7 | }, 8 | basePricePerView: { 9 | type: Number 10 | } 11 | }, 12 | ]) 13 | 14 | 15 | Cities = new Mongo.Collection('cities') 16 | Cities.attachSchema(CitySchema) 17 | -------------------------------------------------------------------------------- /lib/schemas/categories.js: -------------------------------------------------------------------------------- 1 | var CategorySchema = new SimpleSchema({ 2 | // code example: movie-trailer 3 | code: { type: String, unique: true }, 4 | name: { type: String }, 5 | backgroundImages: { 6 | type: [String], 7 | maxCount: 3, 8 | minCount: 3, 9 | }, 10 | }) 11 | 12 | Categories = new Mongo.Collection('categories') 13 | Categories.attachSchema(CategorySchema) 14 | -------------------------------------------------------------------------------- /lib/schemas/time-logs.js: -------------------------------------------------------------------------------- 1 | var TimeLogsSchema = new SimpleSchema({ 2 | driverId: { type: String }, 3 | startTime: { type: Date }, 4 | endTime: { type: Date }, 5 | riders: { 6 | type: Number, 7 | label: 'number of rides', 8 | min: 1 9 | }, 10 | earnings: { type: Number, decimal: false }, 11 | }) 12 | 13 | TimeLogs = new Mongo.Collection('timeLogs') 14 | TimeLogs.attachSchema(TimeLogsSchema) 15 | -------------------------------------------------------------------------------- /lib/before/constants.js: -------------------------------------------------------------------------------- 1 | Constant = {} 2 | 3 | Constant.campaignTypes = [ 'one-off', 'editorial', 'video', 'custom' ] 4 | Constant.campaignTimezoneNames = _.filter( 5 | moment.tz.names(), 6 | function(name) { 7 | return name.match(/^America/) 8 | } 9 | ) 10 | Constant.campaignTimezoneOptions = _.inject( 11 | Constant.campaignTimezoneNames, 12 | function(acc, name) { 13 | acc[name] = name.replace(/^America\//, '') 14 | return acc 15 | }, 16 | {} 17 | ) 18 | 19 | -------------------------------------------------------------------------------- /lib/schemas/channels.js: -------------------------------------------------------------------------------- 1 | var ChannelSchema = new SimpleSchema({ 2 | code: { type: String }, // internal id for gnomad (unique) 3 | type: { type: String, allowedValues: ['read', 'listen', 'watch'] }, 4 | apiCode: { type: String, }, // E.G. 'youtube' 5 | apiId: { type: String, }, // E.G. 'FoxSports' youtube channel id 6 | cities: { type: [String], defaultValue: [], optional: true }, 7 | categoryCode: { type: String, } 8 | }) 9 | 10 | Channels = new Mongo.Collection('channels') 11 | Channels.attachSchema(ChannelSchema) 12 | -------------------------------------------------------------------------------- /lib/schemas/contents.js: -------------------------------------------------------------------------------- 1 | var ContentSchema = new SimpleSchema([ 2 | SharedSchemas.CreatedAtSchema, 3 | SharedSchemas.UpdatedAtSchema, 4 | { 5 | title: { 6 | type: String 7 | }, 8 | description: { 9 | type: String, 10 | optional: true 11 | }, 12 | backgroundImage: { 13 | type: String, 14 | optional: true 15 | }, 16 | channelId: { 17 | type: String 18 | }, 19 | resourceId: { 20 | type: String 21 | }, 22 | } 23 | ]) 24 | 25 | Contents = new Mongo.Collection('contents') 26 | Contents.attachSchema(ContentSchema) 27 | -------------------------------------------------------------------------------- /lib/schemas/images.js: -------------------------------------------------------------------------------- 1 | var FIVE_MB = 5000000 2 | var ONE_MB = 1000000 3 | 4 | Images = new FS.Collection("images", { 5 | filter: { 6 | maxSize: FIVE_MB, 7 | allow: { 8 | extensions: ['jpg'] 9 | } 10 | }, 11 | stores: [new FS.Store.GridFS("images", { 12 | transformWrite: function (fileObj, readStream, writeStream) { 13 | var size = fileObj.original.size 14 | 15 | // compress image if it is larger than ONE_MB 16 | if (size > ONE_MB) { 17 | var image = gm(readStream, fileObj) 18 | // About 'quality', please refer to: 19 | // http://www.graphicsmagick.org/GraphicsMagick.html#details-quality 20 | // quality ranges from 0 to 100 21 | 22 | // About the relationship between quality and compression ratio 23 | // http://stackoverflow.com/questions/3471663/jpeg-compression-ratio 24 | // There is not a one on one relationship between them. 25 | // Therefore we use a fixed ratio to simplify this problem 26 | 27 | image 28 | .compress('JPEG') 29 | .quality(75) 30 | .stream() 31 | .pipe(writeStream) 32 | } else { 33 | readStream.pipe(writeStream) 34 | } 35 | } 36 | })] 37 | }) 38 | -------------------------------------------------------------------------------- /lib/schemas/0-shared-schemas.js: -------------------------------------------------------------------------------- 1 | SimpleSchema.messages({ 2 | lonOutOfRange: '[label] longitude should be between -90 and 90', 3 | latOutOfRange: '[label] latitude should be between -180 and 180', 4 | }) 5 | 6 | var CreatedAtSchema = new SimpleSchema({ 7 | createdAt: { 8 | type: Date, 9 | autoValue: function() { 10 | if (this.isInsert) { 11 | return new Date; 12 | } else if (this.isUpsert) { 13 | return {$setOnInsert: new Date}; 14 | } else { 15 | this.unset(); 16 | } 17 | } 18 | } 19 | }) 20 | 21 | var UpdatedAtSchema = new SimpleSchema({ 22 | updatedAt: { 23 | type: Date, 24 | autoValue: function() { 25 | return new Date() 26 | }, 27 | } 28 | }) 29 | 30 | var PointSchema = new SimpleSchema({ 31 | type: { 32 | type: String, 33 | allowedValues: ['Point'] 34 | }, 35 | coordinates: { 36 | type: [Number], 37 | decimal: true, 38 | minCount: 2, 39 | maxCount: 2, 40 | custom: function () { 41 | lon = this.value[0] 42 | lat = this.value[1] 43 | 44 | if(lon <= -180 || lon >= 180) { 45 | return "latOutOfRange" 46 | } 47 | if(lat <= -90 || lat >= 90) { 48 | return "lonOutOfRange" 49 | } 50 | } 51 | }, 52 | }) 53 | 54 | // NOTE: currently we removed the option in geolocation 55 | // index: '2dsphere' 56 | // becuase simple schema try to create a duplicated index int meteor 1.5.1 57 | // in the future if we move to another server, we may need to migrate 58 | // the data and the index 59 | var GeolocationSchema = new SimpleSchema({ 60 | geolocation: { 61 | type: PointSchema, 62 | }, 63 | }) 64 | 65 | var LocationSchema = new SimpleSchema([ 66 | GeolocationSchema, 67 | { 68 | name: { 69 | type: String, 70 | }, 71 | timezone: { 72 | type: String, 73 | } 74 | } 75 | ]) 76 | 77 | SharedSchemas = { 78 | CreatedAtSchema: CreatedAtSchema, 79 | UpdatedAtSchema: UpdatedAtSchema, 80 | GeolocationSchema: GeolocationSchema, 81 | LocationSchema: LocationSchema, 82 | } 83 | -------------------------------------------------------------------------------- /lib/schemas/campaigns.js: -------------------------------------------------------------------------------- 1 | var hasAutoForm = Package['aldeed:autoform'] 2 | 3 | var VideoSchema = new SimpleSchema({ 4 | type: { 5 | type: String, 6 | allowedValues: ['youtube', 'vimeo'], 7 | label: 'Video Type', 8 | }, 9 | id: { 10 | type: String, 11 | label: 'Video Id', 12 | } 13 | }) 14 | 15 | var CampaignSchema = new SimpleSchema({ 16 | advertiserId: { 17 | type: String 18 | }, 19 | name: { 20 | type: String, 21 | label: 'NAME YOUR CAMPAIGN' 22 | }, 23 | type: { 24 | type: String, 25 | allowedValues: Constant.campaignTypes, 26 | }, 27 | timezone: _.extend( 28 | { 29 | type: String, 30 | allowedValues: Constant.campaignTimezoneNames, 31 | }, 32 | hasAutoForm ? {autoform: { options: Constant.campaignTimezoneOptions }} : {} 33 | ), 34 | startDate: { 35 | type: Date, 36 | custom: function () { 37 | var startDate = this.field('startDate').value 38 | if (!startDate) { 39 | return 'required' 40 | } else if (startDate < moment().add(-1, 'day').startOf('day').toDate()) { 41 | return 'currentOrFutureDate' 42 | } 43 | }, 44 | }, 45 | endDate: { 46 | type: Date, 47 | custom: function () { 48 | var startDate = this.field('startDate').value 49 | var endDate = this.field('endDate').value 50 | 51 | if (!endDate) { 52 | return 'required' 53 | } else if (startDate > endDate) { 54 | return 'laterDate' 55 | } 56 | }, 57 | 58 | }, 59 | startTime: { 60 | // minutes offset 61 | type: Number, 62 | decimal: false 63 | }, 64 | endTime: { 65 | // minutes offset 66 | type: Number, 67 | decimal: false, 68 | custom: function () { 69 | var startTime = this.field('startTime').value 70 | var endTime = this.field('endTime').value 71 | 72 | if (startTime > endTime) { 73 | return 'laterTime' 74 | } 75 | } 76 | }, 77 | cityId: { 78 | type: String, 79 | label: 'CITY' 80 | }, 81 | areaIds: { 82 | type: [String], 83 | minCount: 1 84 | }, 85 | viewsPerHour: { 86 | // temporary for views 87 | type: Number, 88 | decimal: false, 89 | optional: true 90 | }, 91 | views: { 92 | type: Number, 93 | decimal: false 94 | }, 95 | costPerView: { 96 | type: Number, 97 | decimal: true 98 | }, 99 | costTotal: { 100 | type: Number, 101 | decimal: true 102 | }, 103 | categoryCodes: { 104 | type: [String], 105 | minCount: 1 106 | }, 107 | approved: { 108 | type: Boolean, 109 | defaultValue: false 110 | }, 111 | stripeToken: { 112 | type: String, 113 | optional: true, 114 | autoValue: function(){ 115 | if(!this.isFromTrustedCode){ 116 | return null 117 | } 118 | } 119 | }, 120 | video: { 121 | type: VideoSchema, 122 | optional: true, 123 | custom: function () { 124 | var isVideo = this.field('type').value === 'video' 125 | if (isVideo && !this.value) { 126 | return 'required' 127 | } 128 | }, 129 | }, 130 | webpage: { 131 | type: String, 132 | optional: true, 133 | custom: function () { 134 | var isCustom = this.field('type').value === 'custom' 135 | var isValid = /https?:\/\//.test(this.value) 136 | if (isCustom && !isValid) { 137 | return 'urlFormat' 138 | } 139 | }, 140 | }, 141 | pictures: { 142 | type: [String], 143 | custom: function() { 144 | if(!_.isArray(this.value)) { 145 | return 'required' 146 | } 147 | 148 | var isEditorial = this.field('type').value === 'editorial' 149 | 150 | if(isEditorial) { 151 | if(this.value.length !== 3) { 152 | return 'threePictures' 153 | } 154 | } else { 155 | if(this.value.length !== 1) { 156 | return 'onePicture' 157 | } 158 | } 159 | } 160 | }, 161 | "pictures.$": hasAutoForm ? { 162 | autoform: { 163 | afFieldInput: { 164 | type: 'fileUpload', 165 | collection: 'Images', 166 | accept: 'image/*' 167 | } 168 | } 169 | } : {}, 170 | clicksCount: { 171 | type: Number, 172 | defaultValue: 0 173 | }, 174 | impressionsCount: { 175 | type: Number, 176 | defaultValue: 0 177 | } 178 | }) 179 | 180 | CampaignSchema.messages({ 181 | 'expectedConstructor startTime': '[label] must be a time', 182 | 'expectedConstructor endTime': '[label] must be a time', 183 | 'onePicture': 'You must provide one picture.', 184 | 'threePictures': 'You must provide three pictures.', 185 | 'urlFormat': 'EXAMPLE: http://www.example.com/advertisement', 186 | 'currentOrFutureDate': '[label] should be a present or future date', 187 | 'laterDate': '[label] should be later than or equal to Start date', 188 | 'laterTime': '[label] should be later than Start time' 189 | }) 190 | 191 | Campaigns = new Mongo.Collection('campaigns') 192 | Campaigns.attachSchema(CampaignSchema) 193 | --------------------------------------------------------------------------------