Newer
Older
#
# 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
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.schedule_schema = ScheduleSchema(many=True)
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",
)
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
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)
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
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()