-
Notifications
You must be signed in to change notification settings - Fork 1
/
item.js
224 lines (207 loc) · 6.14 KB
/
item.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
var mongoose = require('mongoose')
, validator = require('mongoose-validator')
, validate = validator.validate
, Promise = require('promise')
, _ = require('underscore')
, _s = require('underscore.string')
, languageCodes = require('./language-codes')
, schemaUtils = require("./utils")
, logger = require("winston");
/**
* Inside validator functions we have access to `this.str`. It's not always
* clear what this will be, so you're advised to check for yourself with a
* handful of values (arrays, objects, etc).
*
* The `isLanguage` validate verifies that the user has given us an ISO code
* that exists in the language codes known to our system.
*/
validator.extend('isLanguage', function () {
return _(_(languageCodes).pluck("code")).contains(this.str);
});
/**
* An Item document represents a piece of data specific to a location. This can
* be either more ephemeral data, like a tweet or incident report, or more
* permanent, like the location of a police station or shelter.
*/
var itemSchema = mongoose.Schema({
createdAt: {
type: Date,
default: Date.now
},
updatedAt: Date,
/*
* The date/time at which this document was published at its original
* source.
*/
publishedAt: Date,
/**
* This is either provided explictly by the data source (like a tweet id),
* or must be generated by CrisisNET based on the third-party content. It
* ensures that we will recognize when we see the same data a second time,
* and can either discard or update our record, whichever is appropriate.
*/
remoteID: {
type: String,
required: true,
index: true
},
/**
* Optional field indicating how long this item should be considered
* active.
*/
activeUntil: Date,
/**
* Serves as a way to filter incoming data into buckets that are most useful
* for API consumers. Temporary data sources might be tweets or Facebook
* updates, while semi-permanent might be the location of relief shelters
* or open grocery stores, and permanent would be the location of a
* permanent physical structure, like a police station.
*/
lifespan: {
type: String,
required: true,
validate: validate('isIn', ['temporary', 'semi-permanent', 'permanent']),
default: 'temporary'
},
content: String,
summary: String,
/**
* Fully-formed URL to publically available image.
*/
image: {
type: String,
validate: validate('isUrl')
},
geo: {
/**
* Known address components.
*/
addressComponents: {
formattedAddress: String,
streetNumber: String,
streetName: String,
streetAddress: String,
neighborhood: String,
adminArea5: String, // city
adminArea4: String, // county
adminArea3: String, // state
adminArea2: String, // region
adminArea1: String, // country
postalCode: String
},
/**
* Note that coordinates should always been longitude, latitude
*/
coords: { type: [Number], index: '2dsphere'},
/**
* How accurate are the coordinates? Defined as radius, in meters.
*/
accuracy: Number,
granularity: {
type: String,
validate: validate('isIn', [
'exact',
'near-exact',
'admin5',
'admin4',
'admin3',
'admin2',
'admin1',
'estimated',
'unclear'
])
},
mentionedPlaces: [String],
/**
* Unlike address components above, these are clues to the location of
* the content.
*/
locationIdentifiers: {
authorLocationName: String,
authorTimeZone: String
}
},
/**
* This is a flat tagging system to categorize item data. CrisisNET maintains
* the list of allowed tags.
*/
tags: [
{
name: {
type: String,
index: true
},
confidence: {
type: Number,
required: true,
default: 1.0
}
}
],
/**
* [ISO code](http://stackoverflow.com/a/20623472/2367526) of the primary
* language of the content.
*/
language: {
code: {
type: String
//validate: validate({passIfEmpty: true}, 'isLanguage')
},
name: String,
nativeName: String
},
/**
* Reference to the Source document that led to this item being retrieved.
*/
source: {
type: String,
index: true
},
license: {
type: String,
required: true,
default: 'unknown',
validate: validate('isIn', ['odbl', 'commercial', 'unknown'])
},
fromURL: {
type: String,
validate: validate('isUrl')
}
});
// Copying common methods, because inheriting from a base schema that inherited
// from `mongoose.Schema` was annoying.
schemaUtils.setCommonFuncs("statics", itemSchema);
schemaUtils.setCommonFuncs("methods", itemSchema);
itemSchema.pre("save",function(next, done) {
var self = this;
// Note timestamp of update
self.updatedAt = Date.now();
// If this is provided, we assume it to be the third-party publish date.
self.publishedAt = self.publishedAt || self.createdAt;
// Create a summary, unless whoever did the transforming beat us
// to it.
self.summary = self.summary || _s.prune(self.content, 100);
// Grab the entire language object for the provided code, assuming a code
// was provided.
if(!_(self.language).isEmpty() && self.language.code) {
self.language = _(languageCodes).findWhere({code: self.language.code});
}
// Don't let any old crap in here. If we didn't get an ISO code, then
// there's nothing more to say.
else {
self.language = null;
}
next();
});
itemSchema.pre('save', function (next) {
var coords = this.geo.coords;
if(_(coords).isNull()) {
this.geo.coords = undefined;
}
if(this.isNew && Array.isArray(coords) && 0 === coords.length) {
this.geo.coords = undefined;
}
next();
});
var Item = mongoose.model('Item', itemSchema);
module.exports = Item;