Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 ###
6 changes: 5 additions & 1 deletion api/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please let's fix the typo boquets, it should be bouquet.

Bouquets, RefreshChannels)


api_v2 = Blueprint('mrmpush_2', __name__, url_prefix="/v2")
Expand All @@ -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/<string:api_type>/<int:bouquet_id>',
strict_slashes=False)
17 changes: 16 additions & 1 deletion api/v2/controllers/boquets/bouquets_controller.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
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):
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
88 changes: 88 additions & 0 deletions api/v2/helpers/bouquets/bouquets_helper.py
Original file line number Diff line number Diff line change
@@ -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():
Expand All @@ -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):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This operation can take a while if you are waiting for a response. Let's consider using Celery to queue requests and create a notifications table to store tasks statuses which users will use to monitor their tasks.

"""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"},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please correct typo appropriatly

'code': 424
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why we are using this HTTP status code? It seems to represent failed dependencies and does not seem to be a standard HTTP status code. Please shed some more light here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to check if the refresh URL endpoint is responding as required. If an error occurs at this point it is to be because of that dependency and not necessarily on our endpoint

}
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'
17 changes: 17 additions & 0 deletions api/v2/helpers/channels/channels_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 0 additions & 5 deletions api/v2/models/bouquets/bouquets_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""

Expand Down
5 changes: 3 additions & 2 deletions api/v2/models/bouquets/bouquets_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
8 changes: 4 additions & 4 deletions api/v2/models/channels/channels_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
13 changes: 7 additions & 6 deletions tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
35 changes: 35 additions & 0 deletions tests/test_bouquets/test_bouquets.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from unittest.mock import patch
from tests.base import BaseTestCase


Expand All @@ -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)