# # Aura Engine # # Copyright (C) 2020 David Trattnig <david.trattnig@subsquare.at> # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # Meta __version__ = '0.0.1' __license__ = "GNU Affero General Public License (AGPL) Version 3" __version_info__ = (0, 0, 1) __author__ = 'David Trattnig <david.trattnig@subsquare.at>' import logging import os, os.path from datetime import datetime, date, timedelta from flask import Flask, Response from flask_caching import Cache from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow from marshmallow import Schema, fields, post_dump from flask_restful import Api, Resource from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin from apispec_webframeworks.flask import FlaskPlugin from libraries.base.logger import AuraLogger from libraries.base.config import AuraConfig from libraries.database.broadcasts import AuraDatabaseModel, Schedule, Playlist, PlaylistEntry, PlaylistEntryMetaData, TrackService # # Initialize the Aura Web App and API. # config = AuraConfig() app = Flask(__name__, static_url_path='', static_folder='contrib/aura-player/public/') app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False app.config["SQLALCHEMY_DATABASE_URI"] = config.get_database_uri() app.config["CACHE_TYPE"] = "simple" app.config["CACHE_DEFAULT_TIMEOUT"] = 0 app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 cache = Cache(app) cors = CORS(app, resources={r"/*": {"origins": "*"}}) db = SQLAlchemy(app) ma = Marshmallow(app) api = Api(app) class EngineApi: """ Provides the Aura Engine API services. """ config = None api = None logger = None trackservice_schema = None def __init__(self, config, api): """ Initializes the API. Args: config (AuraConfig): The Engine configuration. api (Api): The Flask restful API object. """ self.config = config self.logger = AuraLogger(self.config, "engine-api") self.logger = logging.getLogger("engine-api") self.api = api spec.components.schema("TrackService", schema=TrackServiceSchema) # Schema instances EngineApi.trackservice_schema = TrackServiceSchema(many=True) EngineApi.track_schema = TrackServiceSchema() EngineApi.report_schema = ReportSchema(many=True) EngineApi.schedule_schema = ScheduleSchema(many=True) # Define API routes self.api.add_resource(TrackServiceResource, config.api_prefix + "/trackservice/") self.api.add_resource(TrackResource, config.api_prefix + "/trackservice/<int:track_id>") self.api.add_resource(CurrentTrackResource, config.api_prefix + "/trackservice/current") self.api.add_resource(TracksByDayResource, config.api_prefix + "/trackservice/date/<string:date_string>") self.api.add_resource(ReportResource, config.api_prefix + "/report/<string:year_month>") self.api.add_resource(UpcomingSchedulesResource, config.api_prefix + "/schedule/upcoming") self.logger.info("Engine API routes successfully set!") # Static resources @app.route('/trackservice', methods=['GET']) def trackservice(): content = open(os.path.join("contrib/aura-player/public/", "index.html")) return Response(content, mimetype="text/html") # Print the API Spec # TODO Generates HTML for specification # self.logger.info(spec.to_dict()) # self.logger.info(spec.to_yaml()) def run(self): """ Starts the API server. """ # TODO Extend to provide production-grade app server # Set debug=False if you want to use your native IDE debugger self.api.app.run(port=self.config.api_port, debug=False) # # API SPEC # spec = APISpec( title="Swagger API Specification for Aura Engine", version="1.0.0", openapi_version="3.0.2", plugins=[FlaskPlugin(), MarshmallowPlugin()], ) # # API SCHEMA # class TrackServiceSchema(ma.Schema): class Meta: fields = ( "id", "schedule.schedule_id", "schedule.schedule_start", "schedule.schedule_end", "schedule.languages", "schedule.type", "schedule.category", "schedule.topic", "schedule.musicfocus", "schedule.is_repetition", "schedule.show_id", "schedule.show_name", "schedule.show_hosts", "track", "track_start" ) class ScheduleSchema(ma.Schema): class Meta: fields = ( "id", "schedule_id", "schedule_start", "schedule", "show_id", "show_name", "show_hosts", "show_type", ) class ReportSchema(ma.Schema): class Meta: fields = ( "id", "schedule.schedule_id", "schedule.schedule_start", "schedule.schedule_end", "schedule.languages", "schedule.type", "schedule.category", "schedule.topic", "schedule.musicfocus", "schedule.is_repetition", "schedule.show_id", "schedule.show_name", "schedule.show_hosts", "schedule.show_type", "schedule.show_funding_category", "track", "track_start", "playlist_id", "fallback_type", "schedule_fallback_id", "show_fallback_id", "station_fallback_id" ) # # API RESOURCES # class TrackServiceResource(Resource): logger = None def __init__(self): self.logger = logging.getLogger("engine-api") def get(self): today = date.today() today = datetime(today.year, today.month, today.day) tracks = TrackService.select_by_day(today) return EngineApi.trackservice_schema.dump(tracks) class TrackResource(Resource): logger = None def __init__(self): self.logger = logging.getLogger("engine-api") def get(self, track_id): track = TrackService.select_one(track_id) return EngineApi.track_schema.dump(track) class CurrentTrackResource(Resource): logger = None def __init__(self): self.logger = logging.getLogger("engine-api") def get(self, track_id): track = TrackService.select_current() return EngineApi.track_schema.dump(track) class TracksByDayResource(Resource): logger = None def __init__(self): self.logger = logging.getLogger("engine-api") def get(self, date_string): date = datetime.strptime(date_string, "%Y-%m-%d") self.logger.debug("Query track-service by day: %s" % str(date)) tracks = TrackService.select_by_day(date) return EngineApi.trackservice_schema.dump(tracks) class UpcomingSchedulesResource(Resource): logger = None def __init__(self): self.logger = logging.getLogger("engine-api") def get(self): now = datetime.now() self.logger.debug("Query upcoming schedules after %s" % str(now)) schedules = Schedule.select_upcoming(3) return EngineApi.schedule_schema.dump(schedules) class ReportResource(Resource): logger = None def __init__(self): self.logger = logging.getLogger("engine-api") def get(self, year_month): year = int(year_month.split("-")[0]) month = int(year_month.split("-")[1]) first_day = datetime(year, month, 1) next_month = first_day.replace(day=28) + timedelta(days=4) next_month - timedelta(days=next_month.day) self.logger.debug("Query report for month: %s - %s" % (str(first_day), str(next_month))) report = TrackService.select_by_range(first_day, next_month) return EngineApi.report_schema.dump(report) # Initialization calls if __name__ == "__main__": engine_api = EngineApi(config, api) engine_api.run()