Skip to content

Commit

Permalink
send suggestion
Browse files Browse the repository at this point in the history
reset some files
  • Loading branch information
andreformento committed Jun 26, 2021
1 parent b9da14c commit 2c396d4
Show file tree
Hide file tree
Showing 14 changed files with 341 additions and 35 deletions.
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
__pycache__
.coverage
Makefile
README.md
.github/
LICENCE
config/sample.env
config/current.env
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.coverage
__pycache__
config/current.env
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ run-command=(podman run --rm -ti --volume $(PWD):/mnt/code:rw \
--env QUERIDO_DIARIO_DATABASE_CSV=$(QUERIDO_DIARIO_DATABASE_CSV) \
--env PYTHONPATH=/mnt/code \
--env RUN_INTEGRATION_TESTS=$(RUN_INTEGRATION_TESTS) \
--env-file config/current.env \
--user=$(UID):$(UID) $(IMAGE_NAMESPACE)/$(IMAGE_NAME):$(IMAGE_TAG) $1)

wait-for=(podman run --rm -ti --volume $(PWD):/mnt/code:rw \
Expand Down Expand Up @@ -68,6 +69,7 @@ destroy-pod:
podman pod rm --force --ignore $(POD_NAME)

create-pod: destroy-pod
cp --no-clobber config/sample.env config/current.env
podman pod create --publish $(API_PORT):$(API_PORT) \
--publish $(ELASTICSEARCH_PORT1):$(ELASTICSEARCH_PORT1) \
--publish $(ELASTICSEARCH_PORT2):$(ELASTICSEARCH_PORT2) \
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ need to insert data into the database. There is another make target, `make apisq
which open the `psql` and connect to the database. Thus, you can insert data
using some `INSERT INTO ...` statements and test the API. ;)


### Using suggestion endpoint

You need to create a token at [Mailjet](www.mailjet.com) to run
application and send email (put on `config/current.env`).

## Tests

The project uses TDD during development. This means that there are no changes
Expand Down
56 changes: 53 additions & 3 deletions api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
from datetime import date
from typing import List, Optional

from fastapi import FastAPI, Query, Path
from fastapi import FastAPI, Query, Path, Response, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from pydantic import BaseModel, Field

from gazettes import GazetteAccessInterface, GazetteRequest
from suggestions import Suggestion, SuggestionService
from config.config import load_configuration

config = load_configuration()
Expand Down Expand Up @@ -75,8 +76,28 @@ class SortBy(str, Enum):
ASCENDING_DATE = "ascending_date"


<<<<<<< HEAD
class HTTPExceptionMessage(BaseModel):
detail: str
=======
class CreateSuggestionBody(BaseModel):
title: str = Field(
None, title="Suggestion title", description="Suggestion title",
)
email_address: str = Field(
None, title="Email address", description="Email address who is sending email"
)
name: str = Field(
None, title="Name", description="Name who is sending email",
)
content: str = Field(
None, title="Email content", description="Email content with suggestion",
)


class CreatedSuggestionResponse(BaseModel):
status: str
>>>>>>> 747e9c6... send suggestion


def trigger_gazettes_search(
Expand Down Expand Up @@ -295,10 +316,39 @@ async def get_city(territory_id: str = Path(..., description="City's IBGE ID")):
return {"city": city_info}


def configure_api_app(gazettes: GazetteAccessInterface, api_root_path=None):
@app.post(
"/suggestions",
response_model=CreatedSuggestionResponse,
name="Send a suggestion",
description="Send a suggestion to the project",
response_model_exclude_unset=True,
response_model_exclude_none=True,
)
async def add_suggestion(response: Response, body: CreateSuggestionBody):
suggestion = Suggestion(
title=body.title,
email_address=body.email_address,
name=body.name,
content=body.content,
)
if app.suggestion_service.add_suggestion(suggestion):
response.status_code = status.HTTP_200_OK
return {"status": "sent"}
else:
response.status_code = status.HTTP_400_BAD_REQUEST
return {"status": "Problem on sent message"}


def configure_api_app(
gazettes: GazetteAccessInterface,
suggestion_service: SuggestionService,
api_root_path=None,
):
if not isinstance(gazettes, GazetteAccessInterface):
raise Exception("Only GazetteAccessInterface object are accepted")
if api_root_path is not None and type(api_root_path) != str:
raise Exception("Invalid api_root_path")
app.gazettes = gazettes
app.root_path = api_root_path
app.suggestion_service = suggestion_service
z
15 changes: 15 additions & 0 deletions config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ def _load_boolean(cls, key, default=False):
return False
return default

self.suggestion_mailjet_rest_api_key = os.environ.get(
"SUGGESTION_MAILJET_REST_API_KEY", ""
)
self.suggestion_mailjet_rest_api_secret = os.environ.get(
"SUGGESTION_MAILJET_REST_API_SECRET", ""
)
self.suggestion_email_sender = os.environ.get("SUGGESTION_EMAIL_SENDER", "")
self.suggestion_email_recipient = os.environ.get(
"SUGGESTION_EMAIL_RECIPIENT", ""
)
self.suggestion_name = os.environ.get("SUGGESTION_NAME", "")
self.suggestion_mailjet_custom_id = os.environ.get(
"SUGGESTION_MAILJET_CUSTOM_ID", ""
)


def load_configuration():
return Configuration()
6 changes: 6 additions & 0 deletions config/sample.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
SUGGESTION_MAILJET_REST_API_KEY=mailjet.com
SUGGESTION_MAILJET_REST_API_SECRET=mailjet.com
SUGGESTION_EMAIL_SENDER=[email protected]
SUGGESTION_EMAIL_RECIPIENT=[email protected]
SUGGESTION_NAME=ok
SUGGESTION_MAILJET_CUSTOM_ID=AppCustomID
11 changes: 10 additions & 1 deletion main/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@
from index import create_elasticsearch_data_mapper
from config import load_configuration
from database import create_database_interface
from suggestions import create_suggestion_service

configuration = load_configuration()
datagateway = create_elasticsearch_data_mapper(configuration.host, configuration.index)
database = create_database_interface()
gazettes_interface = create_gazettes_interface(datagateway, database)
configure_api_app(gazettes_interface, configuration.root_path)
suggestion_service = create_suggestion_service(
suggestion_mailjet_rest_api_key=configuration.suggestion_mailjet_rest_api_key,
suggestion_mailjet_rest_api_secret=configuration.suggestion_mailjet_rest_api_secret,
suggestion_email_sender=configuration.suggestion_email_sender,
suggestion_email_recipient=configuration.suggestion_email_recipient,
suggestion_name=configuration.suggestion_name,
suggestion_mailjet_custom_id=configuration.suggestion_mailjet_custom_id,
)
configure_api_app(gazettes_interface, suggestion_service, configuration.root_path)

uvicorn.run(app, host="0.0.0.0", port=8080, root_path=configuration.root_path)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ uvicorn==0.11.8
psycopg2==2.8.5
SQLAlchemy==1.3.19
elasticsearch==7.9.1
mailjet-rest==1.3.4
7 changes: 7 additions & 0 deletions suggestions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .model import Suggestion

from .service import (
SuggestionService,
MailjetSuggestionService, # only for test
create_suggestion_service,
)
26 changes: 26 additions & 0 deletions suggestions/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class Suggestion:
"""
Object containing the data to suggest
"""

def __init__(
self, title, email_address, name, content,
):
self.title = title
self.email_address = email_address
self.name = name
self.content = content

def __hash__(self):
return hash((self.title, self.email_address, self.name, self.content,))

def __eq__(self, other):
return (
self.title == other.title
and self.email_address == other.email_address
and self.name == other.name
and self.content == other.content
)

def __repr__(self):
return f"Suggestion({self.title}, {self.email_address}, {self.name}, {self.content})"
87 changes: 87 additions & 0 deletions suggestions/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import abc
import logging

from mailjet_rest import Client

from .model import Suggestion


class SuggestionService(abc.ABC):
"""
Service to send a suggestion
"""

@abc.abstractmethod
def add_suggestion(self, suggestion: Suggestion):
"""
Method to send a suggestion
"""


class MailjetSuggestionService(SuggestionService):
def __init__(
self,
mailjet_client: Client,
suggestion_email_sender: str,
suggestion_email_recipient: str,
suggestion_name: str,
suggestion_mailjet_custom_id: str,
):
self.mailjet_client = mailjet_client
self.suggestion_email_sender = suggestion_email_sender
self.suggestion_email_recipient = suggestion_email_recipient
self.suggestion_name = suggestion_name
self.suggestion_mailjet_custom_id = suggestion_mailjet_custom_id
self.logger = logging.getLogger(__name__)

def add_suggestion(self, suggestion: Suggestion):
data = {
"Messages": [
{
"From": {
"Email": self.suggestion_email_sender,
"Name": suggestion.name,
},
"To": [
{
"Email": self.suggestion_email_recipient,
"Name": self.suggestion_name,
}
],
"Subject": suggestion.title,
"TextPart": f"From <{suggestion.email_address}>:\n\n{suggestion.content}",
"CustomID": self.suggestion_mailjet_custom_id,
}
]
}
result = self.mailjet_client.send.create(data=data)

self.logger.debug(f"Suggestion body response {result.json()}")
if 200 <= result.status_code <= 299:
self.logger.info(f"Suggestion created for {suggestion.email_address}")
return True
else:
self.logger.error(
f"Error on send email {suggestion.email_address}. Status code response: {result.status_code}"
)
return False


def create_suggestion_service(
suggestion_mailjet_rest_api_key: str,
suggestion_mailjet_rest_api_secret: str,
suggestion_email_sender: str,
suggestion_email_recipient: str,
suggestion_name: str,
suggestion_mailjet_custom_id: str,
) -> SuggestionService:
return MailjetSuggestionService(
mailjet_client=Client(
auth=(suggestion_mailjet_rest_api_key, suggestion_mailjet_rest_api_secret),
version="v3.1",
),
suggestion_email_sender=suggestion_email_sender,
suggestion_email_recipient=suggestion_email_recipient,
suggestion_name=suggestion_name,
suggestion_mailjet_custom_id=suggestion_mailjet_custom_id,
)
Loading

0 comments on commit 2c396d4

Please sign in to comment.