Skip to content

Commit

Permalink
New internal APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
lalinsky committed Feb 18, 2016
1 parent d951ee2 commit 4c7a0c4
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 24 deletions.
10 changes: 9 additions & 1 deletion acoustid/api/errors.py
Expand Up @@ -22,7 +22,8 @@
ERROR_SERVICE_UNAVAILABLE = 13
ERROR_TOO_MANY_REQUESTS = 14
ERROR_INVALID_MUSICBRAINZ_ACCESS_TOKEN = 15
ERROR_INSECURE_REQUEST = 14
ERROR_INSECURE_REQUEST = 16
ERROR_UNKNOWN_APPLICATION = 17


class WebServiceError(Exception):
Expand Down Expand Up @@ -157,3 +158,10 @@ class InsecureRequestError(WebServiceError):
def __init__(self):
message = 'only requests over HTTPS are allowed here'
WebServiceError.__init__(self, ERROR_INSECURE_REQUEST, message)


class UnknownApplicationError(WebServiceError):

def __init__(self):
message = 'unknown application'
WebServiceError.__init__(self, ERROR_UNKNOWN_APPLICATION, message)
95 changes: 94 additions & 1 deletion acoustid/api/v2/internal.py
@@ -1,6 +1,7 @@
# Copyright (C) 2012 Lukas Lalinsky
# Distributed under the MIT license, see the LICENSE file for details.

import datetime
import logging
from acoustid.data.stats import (
update_lookup_stats,
Expand All @@ -9,6 +10,12 @@
)
from acoustid.data.application import (
find_applications_by_apikeys,
insert_application,
lookup_application_id,
update_application_status,
)
from acoustid.data.account import (
insert_account,
)
from acoustid.api import errors
from acoustid.api.v2 import APIHandler, APIHandlerParams
Expand Down Expand Up @@ -86,6 +93,13 @@ def parse(self, values, conn):
self.applications = find_applications_by_apikeys(conn, apikeys)
if not self.applications:
raise errors.InvalidAPIKeyError()
self.from_date = values.get('from')
if self.from_date is not None:
self.from_date = datetime.datetime.strptime(self.from_date, '%Y-%m-%d').date()
self.to_date = values.get('to')
if self.to_date is not None:
self.to_date = datetime.datetime.strptime(self.to_date, '%Y-%m-%d').date()
self.days = values.get('days', type=int)


class LookupStatsHandler(APIHandler):
Expand All @@ -97,7 +111,86 @@ def _handle_internal(self, params):
logger.warning('Invalid cluster secret')
raise errors.NotAllowedError()
application_ids = dict((app['id'], app['apikey']) for app in params.applications)
stats = find_application_lookup_stats_multi(self.conn, application_ids.keys())
kwargs = {}
if params.from_date is not None:
kwargs['from_date'] = params.from_date
if params.to_date is not None:
kwargs['to_date'] = params.to_date
if params.days is not None:
kwargs['days'] = params.days
stats = find_application_lookup_stats_multi(self.conn, application_ids.keys(), **kwargs)
for entry in stats:
entry['date'] = entry['date'].strftime('%Y-%m-%d')
return {'stats': stats}


class CreateAccountHandlerParams(APIHandlerParams):

def parse(self, values, conn):
super(CreateAccountHandlerParams, self).parse(values, conn)
self.secret = values.get('secret')


class CreateAccountHandler(APIHandler):

params_class = CreateAccountHandlerParams

def _handle_internal(self, params):
if self.cluster.secret != params.secret:
logger.warning('Invalid cluster secret')
raise errors.NotAllowedError()
account_id, account_api_key = insert_account(self.conn, {
'name': 'External User',
'anonymous': True,
})
return {'id': account_id, 'api_key': account_api_key}


class CreateApplicationHandlerParams(APIHandlerParams):

def parse(self, values, conn):
super(CreateApplicationHandlerParams, self).parse(values, conn)
self.secret = values.get('secret')
self.account_id = values.get('account_id', type=int)
self.name = values.get('name')
self.version = values.get('version')


class CreateApplicationHandler(APIHandler):

params_class = CreateApplicationHandlerParams

def _handle_internal(self, params):
if self.cluster.secret != params.secret:
logger.warning('Invalid cluster secret')
raise errors.NotAllowedError()
application_id, application_api_key = insert_application(self.conn, {
'account_id': params.account_id,
'name': params.name,
'version': params.version,
})
return {'id': application_id, 'api_key': application_api_key}


class UpdateApplicationStatusHandlerParams(APIHandlerParams):

def parse(self, values, conn):
super(UpdateApplicationStatusHandlerParams, self).parse(values, conn)
self.secret = values.get('secret')
self.account_id = values.get('account_id', type=int)
self.application_id = values.get('application_id', type=int)
if not lookup_application_id(conn, self.application_id, self.account_id):
raise errors.UnknownApplicationError()
self.active = values.get('active', type=bool)


class UpdateApplicationStatusHandler(APIHandler):

params_class = UpdateApplicationStatusHandlerParams

def _handle_internal(self, params):
if self.cluster.secret != params.secret:
logger.warning('Invalid cluster secret')
raise errors.NotAllowedError()
update_application_status(self.conn, params.application_id, params.active)
return {'id': params.application_id, 'active': params.active}
21 changes: 19 additions & 2 deletions acoustid/data/application.py
Expand Up @@ -14,6 +14,13 @@ def lookup_application_id_by_apikey(conn, apikey):
return conn.execute(query).scalar()


def lookup_application_id(conn, application_id, account_id=None):
query = sql.select([schema.application.c.id], schema.application.c.id == application_id)
if account_id is not None:
query = query.where(schema.application.c.account_id == account_id)
return conn.execute(query).scalar()


def find_applications_by_account(conn, account_id):
query = schema.application.select(schema.application.c.account_id == account_id)
query = query.order_by(schema.application.c.name)
Expand All @@ -29,18 +36,19 @@ def insert_application(conn, data):
"""
Insert a new application into the database
"""
api_key = generate_api_key()
with conn.begin():
insert_stmt = schema.application.insert().values({
'name': data['name'],
'version': data['version'],
'email': data.get('email'),
'website': data.get('website'),
'account_id': data['account_id'],
'apikey': generate_api_key(),
'apikey': api_key,
})
id = conn.execute(insert_stmt).inserted_primary_key[0]
logger.debug("Inserted application %r with data %r", id, data)
return id
return id, api_key


def update_application(conn, id, data):
Expand All @@ -56,3 +64,12 @@ def update_application(conn, id, data):
logger.debug("Updated application %r with data %r", id, data)
return id


def update_application_status(conn, id, active):
data = {'active': active}
with conn.begin():
update_stmt = schema.application.update().where(schema.application.c.id == id)
update_stmt = update_stmt.values(data)
conn.execute(update_stmt)
logger.debug("Updated application %r with data %r", id, data)
return id
40 changes: 22 additions & 18 deletions acoustid/data/stats.py
Expand Up @@ -174,24 +174,28 @@ def update_user_agent_stats(db, application_id, date, user_agent, ip, count):
db.execute(stmt)


def find_application_lookup_stats_multi(conn, application_ids):
query = """
SELECT
date,
sum(count_hits) AS count_hits,
sum(count_nohits) AS count_nohits,
sum(count_hits) + sum(count_nohits) AS count
FROM stats_lookups
WHERE
application_id IN ({ids}) AND
date > now() - INTERVAL '30 day' AND date < date(now())
GROUP BY date
ORDER BY date
""".format(ids=",".join(["%s"] * len(application_ids)))
stats = []
for row in conn.execute(query, application_ids):
stats.append(dict(row))
return stats
def find_application_lookup_stats_multi(conn, application_ids, from_date=None, to_date=None, days=30):
query = sql.select([
schema.stats_lookups.c.date,
sql.func.sum(schema.stats_lookups.c.count_hits).label('count_hits'),
sql.func.sum(schema.stats_lookups.c.count_nohits).label('count_nohits'),
sql.func.sum(schema.stats_lookups.c.count_hits + schema.stats_lookups.c.count_nohits).label('count'),
], from_obj=schema.stats_lookups).group_by(schema.stats_lookups.c.date)

if to_date is not None:
query = query.where(schema.stats_lookups.c.date <= to_date)
else:
query = query.where(schema.stats_lookups.c.date < sql.func.date(sql.func.now()))

if from_date is not None:
query = query.where(schema.stats_lookups.c.date >= from_date)
else:
query = query.where(schema.stats_lookups.c.date > (sql.func.date(sql.func.now()) - datetime.timedelta(days=days)))

if application_ids:
query = query.where(schema.stats_lookups.c.application_id.in_(application_ids))

return [dict(row) for row in conn.execute(query)]


def find_application_lookup_stats(conn, application_id):
Expand Down
1 change: 0 additions & 1 deletion acoustid/db.py
Expand Up @@ -8,4 +8,3 @@ class DatabaseContext(object):

def __init__(self, bind):
self.session = Session(bind=bind)

4 changes: 3 additions & 1 deletion acoustid/server.py
Expand Up @@ -34,6 +34,9 @@
Rule('/update_lookup_stats', endpoint=api.v2.internal.UpdateLookupStatsHandler),
Rule('/update_user_agent_stats', endpoint=api.v2.internal.UpdateUserAgentStatsHandler),
Rule('/lookup_stats', endpoint=api.v2.internal.LookupStatsHandler),
Rule('/create_account', endpoint=api.v2.internal.CreateAccountHandler),
Rule('/create_application', endpoint=api.v2.internal.CreateApplicationHandler),
Rule('/update_application_status', endpoint=api.v2.internal.UpdateApplicationStatusHandler),
]),
]),
])
Expand Down Expand Up @@ -96,4 +99,3 @@ def make_application(config_path):
app = Server(config_path)
app = GzipRequestMiddleware(app)
return app

0 comments on commit 4c7a0c4

Please sign in to comment.