1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
12 from google.appengine.api import memcache
13 from google.appengine.api import users
14 from google.appengine.datastore import datastore_query
15 from google.appengine.ext import ndb
17 LOGGER = logging.getLogger(__name__)
20 class DateTimeEncoder(json.JSONEncoder):
21 def default(self, obj):
22 if isinstance(obj, datetime.datetime):
23 return calendar.timegm(obj.timetuple())
24 # Let the base class default method raise the TypeError.
25 return json.JSONEncoder.default(self, obj)
28 class AlertsJSON(ndb.Model):
29 type = ndb.StringProperty()
30 json = ndb.BlobProperty(compressed=True)
31 date = ndb.DateTimeProperty(auto_now_add=True)
34 class AlertsHandler(webapp2.RequestHandler):
35 ALERTS_TYPE = 'alerts'
37 # Has no 'response' member.
38 # pylint: disable=E1101
39 def send_json_headers(self):
40 self.response.headers.add_header('Access-Control-Allow-Origin', '*')
41 self.response.headers['Content-Type'] = 'application/json'
43 # Has no 'response' member.
44 # pylint: disable=E1101
45 def send_json_data(self, data):
46 self.send_json_headers()
47 self.response.write(data)
49 def generate_json_dump(self, alerts):
50 return json.dumps(alerts, cls=DateTimeEncoder, indent=1)
52 def get_from_memcache(self, memcache_key):
53 compressed = memcache.get(memcache_key)
55 self.send_json_headers()
57 uncompressed = zlib.decompress(compressed)
58 self.send_json_data(uncompressed)
61 self.get_from_memcache(AlertsHandler.ALERTS_TYPE)
63 def post_to_history(self, alerts_type, alerts):
64 last_query = AlertsJSON.query().filter(AlertsJSON.type == alerts_type)
65 last_entry = last_query.order(-AlertsJSON.date).get()
66 last_alerts = json.loads(last_entry.json) if last_entry else {}
68 # Only changes to the fields with 'alerts' in the name should cause a
69 # new history entry to be saved.
70 def alert_fields(alerts_json):
72 for key, value in alerts_json.iteritems():
74 filtered_json[key] = value
77 if alert_fields(last_alerts) != alert_fields(alerts):
78 new_entry = AlertsJSON(
79 json=self.generate_json_dump(alerts),
83 # Has no 'response' member.
84 # pylint: disable=E1101
85 def post_to_memcache(self, memcache_key, alerts):
86 uncompressed = self.generate_json_dump(alerts)
88 compressed = zlib.compress(uncompressed, compression_level)
89 memcache.set(memcache_key, compressed)
91 def parse_alerts(self, alerts_json):
93 alerts = json.loads(alerts_json)
95 warning = 'content field was not JSON'
96 self.response.set_status(400, warning)
100 alerts.update({'date': datetime.datetime.utcnow()})
104 def update_alerts(self, alerts_type):
105 alerts = self.parse_alerts(self.request.get('content'))
107 self.post_to_memcache(alerts_type, alerts)
108 self.post_to_history(alerts_type, alerts)
111 self.update_alerts(AlertsHandler.ALERTS_TYPE)
114 class AlertsHistory(webapp2.RequestHandler):
115 MAX_LIMIT_PER_PAGE = 100
117 def get_entry(self, query, key):
121 self.response.set_status(400, 'Invalid key format')
124 ndb_key = ndb.Key(AlertsJSON, key)
125 result = query.filter(AlertsJSON.key == ndb_key).get()
127 return json.loads(result.json)
129 self.response.set_status(404, 'Failed to find key %s' % key)
132 def get_list(self, query):
133 cursor = self.request.get('cursor')
135 cursor = datastore_query.Cursor(urlsafe=cursor)
137 limit = int(self.request.get('limit', self.MAX_LIMIT_PER_PAGE))
138 limit = min(self.MAX_LIMIT_PER_PAGE, limit)
141 alerts, next_cursor, has_more = query.fetch_page(limit,
144 alerts, next_cursor, has_more = query.fetch_page(limit)
147 'has_more': has_more,
148 'cursor': next_cursor.urlsafe() if next_cursor else '',
149 'history': [alert.key.integer_id() for alert in alerts]
152 def get(self, key=None):
153 query = AlertsJSON.query().order(-AlertsJSON.date)
156 user = users.get_current_user()
157 result_json['login-url'] = users.create_login_url(self.request.uri)
159 # Return only public alerts for non-internal users.
160 if not user or not user.email().endswith('@google.com'):
161 query = query.filter(AlertsJSON.type == AlertsHandler.ALERTS_TYPE)
164 result_json.update(self.get_entry(query, key))
166 result_json.update(self.get_list(query))
168 self.response.headers['Content-Type'] = 'application/json'
169 self.response.out.write(json.dumps(result_json))
172 app = webapp2.WSGIApplication([
173 ('/alerts', AlertsHandler),
174 ('/alerts-history', AlertsHistory),
175 ('/alerts-history/(.*)', AlertsHistory),