diff --git a/alembic/versions/9f4ff449f825_allow_null_values_on_channels_table.py b/alembic/versions/9f4ff449f825_allow_null_values_on_channels_table.py new file mode 100644 index 0000000..2fd2f1e --- /dev/null +++ b/alembic/versions/9f4ff449f825_allow_null_values_on_channels_table.py @@ -0,0 +1,46 @@ +"""allow null values on channels table + +Revision ID: 9f4ff449f825 +Revises: ffc544c2bbe6 +Create Date: 2019-09-11 14:19:44.002569 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '9f4ff449f825' +down_revision = 'ffc544c2bbe6' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("channels") as batch: + batch.alter_column('channel_id', + existing_type=sa.VARCHAR(), + nullable=True) + batch.alter_column('extra_atrributes', + existing_type=sa.VARCHAR(), + nullable=True) + batch.alter_column('resource_id', + existing_type=sa.VARCHAR(), + nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("channels") as batch: + batch.alter_column('resource_id', + existing_type=sa.VARCHAR(), + nullable=False) + batch.alter_column('extra_atrributes', + existing_type=sa.VARCHAR(), + nullable=False) + batch.alter_column('channel_id', + existing_type=sa.VARCHAR(), + nullable=False) + # ### end Alembic commands ### diff --git a/api/v2/__init__.py b/api/v2/__init__.py index 0630251..0d38ff9 100644 --- a/api/v2/__init__.py +++ b/api/v2/__init__.py @@ -2,7 +2,8 @@ from flask import Blueprint from api.v2.controllers.channels.channels_controller import Channels -from api.v2.controllers.boquets.bouquets_controller import Bouquets +from api.v2.controllers.boquets.bouquets_controller import ( + Bouquets, RefreshChannels) api_v2 = Blueprint('mrmpush_2', __name__, url_prefix="/v2") @@ -14,3 +15,6 @@ mrm_push.add_resource(Channels, '/channels', strict_slashes=False) mrm_push.add_resource(Bouquets, '/bouquets', strict_slashes=False) +mrm_push.add_resource( + RefreshChannels, '/refresh//', + strict_slashes=False) diff --git a/api/v2/controllers/boquets/bouquets_controller.py b/api/v2/controllers/boquets/bouquets_controller.py index e843717..e2d28b0 100644 --- a/api/v2/controllers/boquets/bouquets_controller.py +++ b/api/v2/controllers/boquets/bouquets_controller.py @@ -1,6 +1,7 @@ from flask import make_response, jsonify from flask_restful import Resource -from api.v2.helpers.bouquets.bouquets_helper import query_all_bouquets +from api.v2.helpers.bouquets.bouquets_helper import ( + query_all_bouquets, refresh_bouquet_channels) class Bouquets(Resource): @@ -8,3 +9,17 @@ def get(self): bouquets = query_all_bouquets() return make_response(jsonify({"bouquets": bouquets}), 200) + + +class RefreshChannels(Resource): + def post(self, api_type, bouquet_id): + response = make_response( + jsonify( + {'Error': 'Endpoint accepts only graphql_api/restful_api'} + ), 404) + if (api_type == 'restful_api') or (api_type == 'graphql_api'): + refresh = refresh_bouquet_channels(api_type, bouquet_id) + response = make_response( + jsonify(refresh['response']), refresh['code']) + + return response diff --git a/api/v2/helpers/bouquets/bouquets_helper.py b/api/v2/helpers/bouquets/bouquets_helper.py index 365809c..fb8d652 100644 --- a/api/v2/helpers/bouquets/bouquets_helper.py +++ b/api/v2/helpers/bouquets/bouquets_helper.py @@ -1,5 +1,9 @@ +import requests from api.v2.models.bouquets.bouquets_model import Bouquets as BouquetsModel +from api.v2.models.channels.channels_model import Channels as ChannelsModel from api.v2.models.bouquets.bouquets_schema import BouquetsSchema +from api.v2.helpers.channels.channels_helper import ( + query_bouquet_channels, query_channel) def query_all_bouquets(): @@ -9,3 +13,87 @@ def query_all_bouquets(): bouquets = BouquetsSchema(many=True).dump(all_bouquets) return bouquets + + +def query_bouquet(bouquet_id): + bouquet_query = BouquetsModel.query.filter_by( + state='active', id=bouquet_id).first() + bouquet = BouquetsSchema().dump(bouquet_query) + return bouquet + + +def refresh_bouquet_channels(api_type, bouquet_id): + """helper function for refreshing channels""" + response = {'response': {'Error': 'Bouquet not found!'}, 'code': 404} + bouquet = query_bouquet(bouquet_id) + if bouquet: + response = {'response': + {"Error": "Refresh endpoint not responding appropriatly"}, + 'code': 424 + } + bouquet_channels = fetch_bouquet_channels( + api_type, bouquet['refresh_url']) + if bouquet_channels: + channel_update = update_channels(bouquet_id, bouquet_channels) + response = {'response': { + "Success": channel_update}, 'code': 200} + + return response + + +def fetch_bouquet_channels(api_type, refresh_url): + if api_type == 'restful_api': + return restful_channels(refresh_url) + + return graphql_channels(refresh_url) + + +def graphql_channels(refresh_url): + """fetch channels from bouquets with grapghql endpoints""" + channels_query = ( + { + "query": + "{ allChannels { channels { calendarId, firebaseToken } } }" + }) + try: + response = requests.get(url=refresh_url, json=channels_query) + channels = response.json()['data']['allChannels']['channels'] + except Exception: + channels = False + return channels + + +def restful_channels(refresh_url): + """fetch channels from bouquets with restful endpoints""" + try: + response = requests.get(refresh_url) + channels = response.json()['channels'] + except Exception: + channels = False + return channels + + +def update_channels(bouquet_id, bouquet_channels): + subscribed_channels = query_bouquet_channels(bouquet_id) + for channel in bouquet_channels: + existing_channel = filter( + lambda subscribed_channel: + subscribed_channel['calendar_id'] == channel['calendarId'], + subscribed_channels) + if not list(existing_channel): + ChannelsModel(channel_id="", calendar_id=channel['calendarId'], + resource_id="", + extra_atrributes=channel['firebaseToken'], + bouquet_id=bouquet_id).save() + + for channel in subscribed_channels: + db_channel = query_channel(channel['id']) + existing_channel = filter( + lambda bouquet_channel: + bouquet_channel['calendarId'] == channel['calendar_id'], + bouquet_channels) + if not list(existing_channel): + db_channel.state = 'deleted' + db_channel.save() + + return 'Bouquet channels refreshed succesfully' diff --git a/api/v2/helpers/channels/channels_helper.py b/api/v2/helpers/channels/channels_helper.py index 3453c72..a01434c 100644 --- a/api/v2/helpers/channels/channels_helper.py +++ b/api/v2/helpers/channels/channels_helper.py @@ -9,3 +9,20 @@ def query_all_channels(): channels = ChannelsSchema(many=True).dump(all_channels) return channels + + +def query_channel(channel_id): + channel = ChannelsModel.query.filter_by( + state='active', id=channel_id).first() + + return channel + + +def query_bouquet_channels(bouquet_id): + all_channels = ChannelsModel.query.filter_by( + state='active', bouquet_id=bouquet_id).all() + bouquet_channels = {} + if all_channels: + bouquet_channels = ChannelsSchema(many=True).dump(all_channels) + + return bouquet_channels diff --git a/api/v2/models/bouquets/bouquets_model.py b/api/v2/models/bouquets/bouquets_model.py index 156964c..4e6df32 100644 --- a/api/v2/models/bouquets/bouquets_model.py +++ b/api/v2/models/bouquets/bouquets_model.py @@ -25,11 +25,6 @@ def __init__(self, **kwargs): self.api_key2 = kwargs['api_key2'] self.auth_credentials = kwargs['auth_credentials'] - def refresh_channels(self): - """Method for refreshing channels""" - - # TODO: add functionality to refresh and check channels using the refreshUrl - def register_channels(self): """Method for registering channels""" diff --git a/api/v2/models/bouquets/bouquets_schema.py b/api/v2/models/bouquets/bouquets_schema.py index 332b43f..8f16769 100644 --- a/api/v2/models/bouquets/bouquets_schema.py +++ b/api/v2/models/bouquets/bouquets_schema.py @@ -5,5 +5,6 @@ class BouquetsSchema(ma.Schema): class Meta: model = BouquetsModel - fields = ("id", "bouquet_id", "api_key1", "api_key2" - "auth_credentials", "bouquet_name", "should_refresh") + fields = ("id", "bouquet_id", "api_key1", "api_key2", + "auth_credentials", "bouquet_name", + "should_refresh", "refresh_url") diff --git a/api/v2/models/channels/channels_model.py b/api/v2/models/channels/channels_model.py index 8853525..2d77a6d 100644 --- a/api/v2/models/channels/channels_model.py +++ b/api/v2/models/channels/channels_model.py @@ -7,11 +7,11 @@ class Channels(Base, Utility): __tablename__ = 'channels' - id = Column(Integer, Sequence('channels_id_seq', start=1, increment=1), primary_key=True) # noqa - channel_id = Column(String, nullable=False) + id = Column(Integer, Sequence('channels_id_seq', start=1, increment=1), primary_key=True) # noqa + channel_id = Column(String, nullable=True) calendar_id = Column(String, nullable=False) - resource_id = Column(String, nullable=False) - extra_atrributes = Column(String, nullable=False) + resource_id = Column(String, nullable=True) + extra_atrributes = Column(String, nullable=True) bouquet_id = Column(Integer) state = Column(Enum(StateType), nullable=False, default="active") diff --git a/tests/base.py b/tests/base.py index 61d838b..c14d8c7 100644 --- a/tests/base.py +++ b/tests/base.py @@ -35,12 +35,13 @@ def setUp(self): calendar_id="calendar@id.com-djfirnfn", resource_id="9ty4bejkkw", extra_atrributes='t284nff94nf', bouquet_id=1) - bouquet= Bouquets(api_key1="2123", - api_key2="treat44", - auth_credentials='fdflfaw4', bouquet_name="Premium", - should_refresh=True, - refresh_url='http://localhost:5000/refresh') - + bouquet = Bouquets(api_key1="2123", + api_key2="treat44", + auth_credentials='fdflfaw4', + bouquet_name="Premium", + should_refresh=True, + refresh_url='http://localhos:8000/mrm') + channel.save() bouquet.save() diff --git a/tests/test_bouquets/test_bouquets.py b/tests/test_bouquets/test_bouquets.py index 7e43155..47a3368 100644 --- a/tests/test_bouquets/test_bouquets.py +++ b/tests/test_bouquets/test_bouquets.py @@ -1,3 +1,4 @@ +from unittest.mock import patch from tests.base import BaseTestCase @@ -6,3 +7,37 @@ def test_get_all_bouquets(self): response = self.app_test.get("/v2/bouquets") self.assertEqual(response.status_code, 200) + + @patch("api.v2.helpers.bouquets.bouquets_helper.graphql_channels") + def test_refresh_graphql_channels(self, mock_get): + channels = [{ + "calendarId": + "andela.com_3734303034@resource.calendar.google.com", + "firebaseToken": "" + }] + mock_get.return_value = channels + response = self.app_test.post("/v2/refresh/graphql_api/1") + + self.assertEqual(response.status_code, 200) + + def test_refresh_with_invalid_graphql_url(self): + response = self.app_test.post("/v2/refresh/graphql_api/1") + + self.assertEqual(response.status_code, 424) + + @patch("api.v2.helpers.bouquets.bouquets_helper.restful_channels") + def test_refresh_restful_channels(self, mock_get): + channels = [{ + "calendarId": + "andela.com_3734303034@resource.calendar.google.com", + "firebaseToken": "" + }] + mock_get.return_value = channels + response = self.app_test.post("/v2/refresh/restful_api/1") + + self.assertEqual(response.status_code, 200) + + def test_refresh_with_invalid_restful_url(self): + response = self.app_test.post("/v2/refresh/restful_api/1") + + self.assertEqual(response.status_code, 424)