Skip to content

Commit

Permalink
Merge pull request #48 from EverythingMe/feature_query_api_key
Browse files Browse the repository at this point in the history
Feature: allow downloading CSV of a query by using an API key
  • Loading branch information
arikfr committed Jan 4, 2014
2 parents b4b61f9 + 36e5df0 commit b9f3d0c
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 27 deletions.
10 changes: 10 additions & 0 deletions rd_service/data/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""
Django ORM based models to describe the data model of re:dash.
"""
import hashlib
import json
import time
from django.db import models
from django.template.defaultfilters import slugify
import utils
Expand Down Expand Up @@ -40,6 +42,7 @@ class Query(models.Model):
description = models.CharField(max_length=4096)
query = models.TextField()
query_hash = models.CharField(max_length=32)
api_key = models.CharField(max_length=40)
ttl = models.IntegerField()
user = models.CharField(max_length=360)
created_at = models.DateTimeField(auto_now_add=True)
Expand All @@ -58,6 +61,7 @@ def to_dict(self, with_result=True, with_stats=False):
'query_hash': self.query_hash,
'ttl': self.ttl,
'user': self.user,
'api_key': self.api_key,
'created_at': self.created_at,
}

Expand Down Expand Up @@ -92,8 +96,14 @@ def all_queries(cls):

def save(self, *args, **kwargs):
self.query_hash = utils.gen_query_hash(self.query)
self._set_api_key()
super(Query, self).save(*args, **kwargs)

def _set_api_key(self):
if not self.api_key:
self.api_key = hashlib.sha1(
u''.join([str(time.time()), self.query, self.user, self.name])).hexdigest()

def __unicode__(self):
return unicode(self.id)

Expand Down
1 change: 1 addition & 0 deletions rd_service/data/tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ CREATE TABLE "queries" (
"description" varchar(4096),
"query" text NOT NULL,
"query_hash" varchar(32) NOT NULL,
"api_key" varchar(40),
"ttl" integer NOT NULL,
"user" varchar(360) NOT NULL,
"created_at" timestamp with time zone NOT NULL
Expand Down
70 changes: 44 additions & 26 deletions rd_service/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,19 @@ def get_current_user(self):
user = self.get_secure_cookie("user")
return user

@tornado.web.authenticated
def prepare(self):
pass

def write_json(self, response, encode=True):
if encode:
response = json.dumps(response, cls=utils.JSONEncoder)
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(response)


class BaseAuthenticatedHandler(BaseHandler):
@tornado.web.authenticated
def prepare(self):
pass


class PingHandler(tornado.web.RequestHandler):
def get(self):
self.write("PONG")
Expand All @@ -79,7 +81,7 @@ def get(self):
self.authenticate_redirect()


class MainHandler(BaseHandler):
class MainHandler(BaseAuthenticatedHandler):
def get(self, *args):
email_md5 = hashlib.md5(self.current_user.lower()).hexdigest()
gravatar_url = "https://www.gravatar.com/avatar/%s?s=40" % email_md5
Expand All @@ -93,15 +95,15 @@ def get(self, *args):
self.render("index.html", user=json.dumps(user))


class QueryFormatHandler(BaseHandler):
class QueryFormatHandler(BaseAuthenticatedHandler):
def post(self):
arguments = json.loads(self.request.body)
query = arguments.get("query", "")

self.write(sqlparse.format(query, reindent=True, keyword_case='upper'))


class StatusHandler(BaseHandler):
class StatusHandler(BaseAuthenticatedHandler):
def get(self):
status = {}
info = self.redis_connection.info()
Expand All @@ -122,7 +124,7 @@ def get(self):
self.write_json(status)


class WidgetsHandler(BaseHandler):
class WidgetsHandler(BaseAuthenticatedHandler):
def post(self, widget_id=None):
widget_properties = json.loads(self.request.body)
widget_properties['options'] = json.dumps(widget_properties['options'])
Expand Down Expand Up @@ -162,7 +164,7 @@ def delete(self, widget_id):
widget.delete()


class DashboardHandler(BaseHandler):
class DashboardHandler(BaseAuthenticatedHandler):
def get(self, dashboard_slug=None):
if dashboard_slug:
dashboard = data.models.Dashboard.objects.prefetch_related('widgets__query__latest_query_data').get(slug=dashboard_slug)
Expand Down Expand Up @@ -195,7 +197,7 @@ def delete(self, dashboard_slug):
dashboard.save()


class QueriesHandler(BaseHandler):
class QueriesHandler(BaseAuthenticatedHandler):
def post(self, id=None):
query_def = json.loads(self.request.body)
if 'created_at' in query_def:
Expand Down Expand Up @@ -226,7 +228,7 @@ def get(self, id=None):
self.write_json([q.to_dict(with_result=False, with_stats=True) for q in data.models.Query.all_queries()])


class QueryResultsHandler(BaseHandler):
class QueryResultsHandler(BaseAuthenticatedHandler):
def get(self, query_result_id):
query_result = self.data_manager.get_query_result_by_id(query_result_id)
if query_result:
Expand All @@ -249,23 +251,25 @@ def post(self, _):
self.write({'job': job.to_dict()})


class JobsHandler(BaseHandler):
def get(self, job_id=None):
if job_id:
# TODO: if finished, include the query result
job = data.Job.load(self.data_manager.redis_connection, job_id)
self.write({'job': job.to_dict()})
else:
raise NotImplemented
class CsvQueryResultsHandler(BaseAuthenticatedHandler):
def get_current_user(self):
user = super(CsvQueryResultsHandler, self).get_current_user()
if not user:
api_key = self.get_argument("api_key", None)
query = data.models.Query.objects.get(pk=self.path_args[0])

def delete(self, job_id):
job = data.Job.load(self.data_manager.redis_connection, job_id)
job.cancel()
if query.api_key and query.api_key == api_key:
user = "API-Key=%s" % api_key

return user

class CsvQueryResultsHandler(BaseHandler):
def get(self, query_result_id):
query_result = self.data_manager.get_query_result_by_id(query_result_id)
def get(self, query_id, result_id=None):
if not result_id:
query = data.models.Query.objects.get(pk=query_id)
if query:
result_id = query.latest_query_data_id

query_result = result_id and self.data_manager.get_query_result_by_id(result_id)
if query_result:
self.set_header("Content-Type", "text/csv; charset=UTF-8")
s = cStringIO.StringIO()
Expand All @@ -286,13 +290,27 @@ def get(self, query_result_id):
self.send_error(404)


class JobsHandler(BaseAuthenticatedHandler):
def get(self, job_id=None):
if job_id:
# TODO: if finished, include the query result
job = data.Job.load(self.data_manager.redis_connection, job_id)
self.write({'job': job.to_dict()})
else:
raise NotImplemented

def delete(self, job_id):
job = data.Job.load(self.data_manager.redis_connection, job_id)
job.cancel()


def get_application(static_path, is_debug, redis_connection, data_manager):
return tornado.web.Application([(r"/", MainHandler),
(r"/ping", PingHandler),
(r"/api/queries/([0-9]*)/results(?:/([0-9]*))?.csv", CsvQueryResultsHandler),
(r"/api/queries/format", QueryFormatHandler),
(r"/api/queries(?:/([0-9]*))?", QueriesHandler),
(r"/api/query_results(?:/([0-9]*))?", QueryResultsHandler),
(r"/api/query_results/(.*?).csv", CsvQueryResultsHandler),
(r"/api/jobs/(.*)", JobsHandler),
(r"/api/widgets(?:/([0-9]*))?", WidgetsHandler),
(r"/api/dashboards(?:/(.*))?", DashboardHandler),
Expand Down
2 changes: 1 addition & 1 deletion rd_ui/app/scripts/controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
if ($scope.queryResult.getId() == null) {
$scope.dataUri = "";
} else {
$scope.dataUri = '/api/query_results/' + $scope.queryResult.getId() + '.csv';
$scope.dataUri = '/api/queries/' + $scope.query.id + '/results/' + $scope.queryResult.getId() + '.csv';
$scope.dataFilename = $scope.query.name.replace(" ", "_") + moment($scope.queryResult.getUpdatedAt()).format("_YYYY_MM_DD") + ".csv";
}
});
Expand Down

0 comments on commit b9f3d0c

Please sign in to comment.