import itertools from flask import ( jsonify, request, Blueprint, current_app ) from sqlalchemy.orm.exc import NoResultFound from app.dao.api_key_dao import ( save_model_api_key, get_model_api_keys, get_unsigned_secret, expire_api_key) from app.dao.services_dao import ( dao_fetch_service_by_id, dao_fetch_all_services, dao_create_service, dao_update_service, dao_fetch_all_services_by_user, dao_add_user_to_service, dao_remove_user_from_service, dao_fetch_stats_for_service, dao_fetch_todays_stats_for_service, dao_fetch_weekly_historical_stats_for_service, dao_fetch_todays_stats_for_all_services ) from app.dao import notifications_dao from app.dao.provider_statistics_dao import get_fragment_count from app.dao.users_dao import get_model_users from app.schemas import ( service_schema, api_key_schema, user_schema, permission_schema, notification_with_template_schema, notifications_filter_schema, detailed_service_schema ) from app.utils import pagination_links from app.errors import ( register_errors, InvalidRequest ) from app.service import statistics service_blueprint = Blueprint('service', __name__) register_errors(service_blueprint) @service_blueprint.route('', methods=['GET']) def get_services(): user_id = request.args.get('user_id', None) if user_id: services = dao_fetch_all_services_by_user(user_id) elif request.args.get('detailed') == 'True': return jsonify(data=get_detailed_services()) else: services = dao_fetch_all_services() data = service_schema.dump(services, many=True).data return jsonify(data=data) @service_blueprint.route('/', methods=['GET']) def get_service_by_id(service_id): if request.args.get('detailed') == 'True': data = get_detailed_service(service_id, today_only=request.args.get('today_only') == 'True') return jsonify(data=data) else: fetched = dao_fetch_service_by_id(service_id) data = service_schema.dump(fetched).data return jsonify(data=data) @service_blueprint.route('', methods=['POST']) def create_service(): data = request.get_json() if not data.get('user_id', None): errors = {'user_id': ['Missing data for required field.']} raise InvalidRequest(errors, status_code=400) user = get_model_users(data['user_id']) data.pop('user_id', None) valid_service = service_schema.load(request.get_json()).data dao_create_service(valid_service, user) return jsonify(data=service_schema.dump(valid_service).data), 201 @service_blueprint.route('/', methods=['POST']) def update_service(service_id): fetched_service = dao_fetch_service_by_id(service_id) current_data = dict(service_schema.dump(fetched_service).data.items()) current_data.update(request.get_json()) update_dict = service_schema.load(current_data).data dao_update_service(update_dict) return jsonify(data=service_schema.dump(fetched_service).data), 200 @service_blueprint.route('//api-key', methods=['POST']) def create_api_key(service_id=None): fetched_service = dao_fetch_service_by_id(service_id=service_id) valid_api_key = api_key_schema.load(request.get_json()).data valid_api_key.service = fetched_service save_model_api_key(valid_api_key) unsigned_api_key = get_unsigned_secret(valid_api_key.id) return jsonify(data=unsigned_api_key), 201 @service_blueprint.route('//api-key/revoke/', methods=['POST']) def revoke_api_key(service_id, api_key_id): expire_api_key(service_id=service_id, api_key_id=api_key_id) return jsonify(), 202 @service_blueprint.route('//api-keys', methods=['GET']) @service_blueprint.route('//api-keys/', methods=['GET']) def get_api_keys(service_id, key_id=None): dao_fetch_service_by_id(service_id=service_id) try: if key_id: api_keys = [get_model_api_keys(service_id=service_id, id=key_id)] else: api_keys = get_model_api_keys(service_id=service_id) except NoResultFound: error = "API key not found for id: {}".format(service_id) raise InvalidRequest(error, status_code=404) return jsonify(apiKeys=api_key_schema.dump(api_keys, many=True).data), 200 @service_blueprint.route('//users', methods=['GET']) def get_users_for_service(service_id): fetched = dao_fetch_service_by_id(service_id) result = user_schema.dump(fetched.users, many=True) return jsonify(data=result.data) @service_blueprint.route('//users/', methods=['POST']) def add_user_to_service(service_id, user_id): service = dao_fetch_service_by_id(service_id) user = get_model_users(user_id=user_id) if user in service.users: error = 'User id: {} already part of service id: {}'.format(user_id, service_id) raise InvalidRequest(error, status_code=400) permissions = permission_schema.load(request.get_json(), many=True).data dao_add_user_to_service(service, user, permissions) data = service_schema.dump(service).data return jsonify(data=data), 201 @service_blueprint.route('//users/', methods=['DELETE']) def remove_user_from_service(service_id, user_id): service = dao_fetch_service_by_id(service_id) user = get_model_users(user_id=user_id) if user not in service.users: error = 'User not found' raise InvalidRequest(error, status_code=404) elif len(service.users) == 1: error = 'You cannot remove the only user for a service' raise InvalidRequest(error, status_code=400) dao_remove_user_from_service(service, user) return jsonify({}), 204 @service_blueprint.route('//fragment/aggregate_statistics') def get_service_provider_aggregate_statistics(service_id): return jsonify(data=get_fragment_count(service_id)) # This is placeholder get method until more thought # goes into how we want to fetch and view various items in history # tables. This is so product owner can pass stories as done @service_blueprint.route('//history', methods=['GET']) def get_service_history(service_id): from app.models import (Service, ApiKey, Template, TemplateHistory, Event) from app.schemas import ( service_history_schema, api_key_history_schema, template_history_schema, event_schema ) service_history = Service.get_history_model().query.filter_by(id=service_id).all() service_data = service_history_schema.dump(service_history, many=True).data api_key_history = ApiKey.get_history_model().query.filter_by(service_id=service_id).all() api_keys_data = api_key_history_schema.dump(api_key_history, many=True).data template_history = TemplateHistory.query.filter_by(service_id=service_id).all() template_data, errors = template_history_schema.dump(template_history, many=True) events = Event.query.all() events_data = event_schema.dump(events, many=True).data data = { 'service_history': service_data, 'api_key_history': api_keys_data, 'template_history': template_data, 'events': events_data} return jsonify(data=data) @service_blueprint.route('//notifications', methods=['GET']) def get_all_notifications_for_service(service_id): data = notifications_filter_schema.load(request.args).data page = data['page'] if 'page' in data else 1 page_size = data['page_size'] if 'page_size' in data else current_app.config.get('PAGE_SIZE') limit_days = data.get('limit_days') include_jobs = data.get('include_jobs', True) pagination = notifications_dao.get_notifications_for_service( service_id, filter_dict=data, page=page, page_size=page_size, limit_days=limit_days, include_jobs=include_jobs) kwargs = request.args.to_dict() kwargs['service_id'] = service_id return jsonify( notifications=notification_with_template_schema.dump(pagination.items, many=True).data, page_size=page_size, total=pagination.total, links=pagination_links( pagination, '.get_all_notifications_for_service', **kwargs ) ), 200 @service_blueprint.route('//notifications/weekly', methods=['GET']) def get_weekly_notification_stats(service_id): service = dao_fetch_service_by_id(service_id) stats = dao_fetch_weekly_historical_stats_for_service(service_id) stats = statistics.format_weekly_notification_stats(stats, service.created_at) return jsonify(data={week.date().isoformat(): statistics for week, statistics in stats.items()}) def get_detailed_service(service_id, today_only=False): service = dao_fetch_service_by_id(service_id) stats_fn = dao_fetch_todays_stats_for_service if today_only else dao_fetch_stats_for_service stats = stats_fn(service_id) service.statistics = statistics.format_statistics(stats) return detailed_service_schema.dump(service).data def get_detailed_services(): services = {service.id: service for service in dao_fetch_all_services()} stats = dao_fetch_todays_stats_for_all_services() for service_id, rows in itertools.groupby(stats, lambda x: x.service_id): services[service_id].statistics = statistics.format_statistics(rows) # if service has not sent anything, query will not have set statistics correctly for service in services.values(): if not hasattr(service, 'statistics'): service.statistics = statistics.create_zeroed_stats_dicts() return detailed_service_schema.dump(services.values(), many=True).data