Skip to content

Commit dc656e2

Browse files
committed
Merge branch 'main' into alex-branch
2 parents 76f8365 + 6768daa commit dc656e2

38 files changed

+634
-219
lines changed

.env.sample

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ POSTGRES_DATABASE=
22
POSTGRES_USER=
33
POSTGRES_PASSWORD=
44
POSTGRES_DATABASE_URL=
5-
5+
AWS_ACCESS_KEY_ID=
6+
AWS_SECRET_ACCESS_KEY=
7+
AWS_REGION=
8+
SES_EMAIL_FROM=

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,18 @@ pdm run ruff format .
243243

244244
All code needs to pass ruff formatting and linting before it can be merged.
245245

246+
### Logging
247+
248+
To add a logger to a new service or file, use the `LOGGER_NAME` function in `app/utilities/constants.py`
249+
250+
```python
251+
from app.utilities.constants import LOGGER_NAME
252+
253+
log = logging.getLogger(LOGGER_NAME("my_service"))
254+
```
255+
256+
If you'd like to create a new logger name in the hierarchy, you'll need to add it to `alembic.ini` under the logger section. Following the pre-existing examples for `logger_uvicorn` for example.
257+
246258
### Frontend
247259

248260
#### Prettier

backend/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,15 @@ To apply the migration, run the following command:
136136
```bash
137137
pdm run alembic upgrade head
138138
```
139+
140+
### Logging
141+
142+
To add a logger to a new service or file, use the `LOGGER_NAME` function in `app/utilities/constants.py`
143+
144+
```python
145+
from app.utilities.constants import LOGGER_NAME
146+
147+
log = logging.getLogger(LOGGER_NAME("my_service"))
148+
```
149+
150+
If you'd like to create a new logger name in the hierarchy, you'll need to add it to `alembic.ini` under the logger section. Following the pre-existing examples for `logger_uvicorn` for example.

backend/alembic.ini

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
6262
# output_encoding = utf-8
6363

6464
# Updated in env.py using the POSTGRES_DATABASE_URL environment variable
65-
# sqlalchemy.url =
65+
# sqlalchemy.url =
6666

6767

6868
[post_write_hooks]
@@ -83,8 +83,10 @@ ruff.executable = %(here)s/.venv/bin/ruff
8383
ruff.options = check --fix REVISION_SCRIPT_FILENAME
8484

8585
# Logging configuration
86+
# Every time you want to define a new sub-logger, you need to add it to loggers or it won't show up.
87+
# Would recommend just using uvicorn."name of area you want to log" to identify a smaller scope
8688
[loggers]
87-
keys = root,sqlalchemy,alembic
89+
keys = root,sqlalchemy,alembic,uvicorn
8890

8991
[handlers]
9092
keys = console
@@ -107,12 +109,17 @@ level = INFO
107109
handlers =
108110
qualname = alembic
109111

112+
[logger_uvicorn]
113+
level = INFO
114+
handlers =
115+
qualname = uvicorn
116+
110117
[handler_console]
111118
class = StreamHandler
112119
args = (sys.stderr,)
113120
level = NOTSET
114121
formatter = generic
115122

116123
[formatter_generic]
117-
format = %(levelname)-5.5s [%(name)s] %(message)s
124+
format = %(levelname)-5.5s [%(name)s] %(message)s
118125
datefmt = %H:%M:%S
Lines changed: 17 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from abc import ABC, abstractmethod
22

3+
from app.schemas.email_template import EmailContent, EmailTemplateType
4+
35

46
class IEmailService(ABC):
57
"""
@@ -8,65 +10,20 @@ class IEmailService(ABC):
810
"""
911

1012
@abstractmethod
11-
def send_email(self, to: str, subject: str, body: str) -> dict:
12-
"""
13-
Sends an email with the given parameters.
14-
15-
:param to: Recipient's email address
16-
:type to: str
17-
:param subject: Subject of the email
18-
:type subject: str
19-
:param body: HTML body content of the email
20-
:type body: str
21-
:return: Provider-specific metadata (like message ID, thread ID, label IDs)
22-
:rtype: dict
23-
:raises Exception: if email was not sent successfully
24-
"""
25-
pass
26-
27-
@abstractmethod
28-
def send_welcome_email(self, recipient: str, user_name: str) -> dict:
29-
"""
30-
Sends a welcome email to the specified user.
31-
32-
:param recipient: Email address of the user
33-
:type recipient: str
34-
:param user_name: Name of the user
35-
:type user_name: str
36-
:return: Provider-specific metadata for the sent email
37-
:rtype: dict
38-
:raises Exception: if email was not sent successfully
39-
"""
40-
pass
41-
42-
@abstractmethod
43-
def send_password_reset_email(self, recipient: str, reset_link: str) -> dict:
44-
"""
45-
Sends a password reset email with the provided reset link.
46-
47-
:param recipient: Email address of the user requesting the reset
48-
:type recipient: str
49-
:param reset_link: Password reset link
50-
:type reset_link: str
51-
:return: Provider-specific metadata for the sent email
52-
:rtype: dict
53-
:raises Exception: if email was not sent successfully
54-
"""
55-
pass
56-
57-
@abstractmethod
58-
def send_notification_email(self, recipient: str, notification_text: str) -> dict:
59-
"""
60-
Sends a notification email to the user with the provided notification text.
61-
Examples of use case include matches completed and ready to view, new messages,
62-
meeting time scheduled, etc.
63-
64-
:param recipient: Email address of the user
65-
:type recipient: str
66-
:param notification_text: The notification content
67-
:type notification_text: str
68-
:return: Provider-specific metadata for the sent email
69-
:rtype: dict
70-
:raises Exception: if email was not sent successfully
13+
def send_email(
14+
self, templateType: EmailTemplateType, content: EmailContent
15+
) -> dict:
16+
"""Send an email using the given template and content with a
17+
respective service provider.
18+
19+
Args:
20+
templateType (EmailTemplateType): Specifies the template
21+
to be used for the email
22+
content (EmailContent): Contains the recipient and data
23+
to be used in the email
24+
25+
Returns:
26+
dict: Provider-specific metadata if any
27+
(like message ID, thread ID, label IDs)
7128
"""
7229
pass
Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from abc import ABC, abstractmethod
22

3+
from app.schemas.email_template import EmailContent, EmailTemplateType
4+
35

46
class IEmailServiceProvider(ABC):
57
"""
@@ -9,22 +11,18 @@ class IEmailServiceProvider(ABC):
911

1012
@abstractmethod
1113
def send_email(
12-
self, recipient: str, subject: str, body_html: str, body_text: str
14+
self, templateType: EmailTemplateType, content: EmailContent
1315
) -> dict:
14-
"""
15-
Sends an email using the provider's service.
16+
"""_summary_
17+
18+
Args:
19+
templateType (EmailTemplate): Helps provider determine which
20+
template to use for the given email
21+
content (EmailContent): Contains the recipient and data to be
22+
used in the email
1623
17-
:param recipient: Email address of the recipient
18-
:type recipient: str
19-
:param subject: Subject of the email
20-
:type subject: str
21-
:param body_html: HTML body content of the email
22-
:type body_html: str
23-
:param body_text: Plain text content of the email
24-
:type body_text: str
25-
:return: Provider-specific metadata related to the sent email
26-
(like message ID, status, etc.)
27-
:rtype: dict
28-
:raises Exception: if the email fails to send
24+
Returns:
25+
dict: Provider-specific metadata if any
26+
(like message ID, thread ID, label IDs)
2927
"""
3028
pass

backend/app/models/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import logging
2+
13
from alembic import command
24
from alembic.config import Config
35

6+
from app.utilities.constants import LOGGER_NAME
7+
48
# Make sure all models are here to reflect all current models
59
# when autogenerating new migration
610
from .Base import Base
@@ -13,8 +17,12 @@
1317
# Used to avoid import errors for the models
1418
__all__ = ["Base", "User", "Role", "Schedule", "ScheduleStatus", "TimeBlock" ]
1519

20+
log = logging.getLogger(LOGGER_NAME("models"))
21+
1622

1723
def run_migrations():
24+
log.info("Running run_migrations in models/__init__ on server startup")
25+
1826
alembic_cfg = Config("alembic.ini")
1927
# Emulates `alembic upgrade head` to migrate up to latest revision
2028
command.upgrade(alembic_cfg, "head")
Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
from fastapi import APIRouter, Depends
44

55
from app.interfaces.email_service import IEmailService
6+
from app.schemas.email_template import EmailContent, EmailTemplateType, MockEmailData
7+
from app.services.email.amazon_ses_provider import (
8+
get_email_service_provider,
9+
)
610
from app.services.email.email_service import EmailService
7-
from app.services.email.email_service_provider import AmazonSESEmailProvider
811

912
router = APIRouter(
1013
prefix="/email",
@@ -13,16 +16,19 @@
1316

1417

1518
def get_email_service() -> IEmailService:
16-
email_provider = AmazonSESEmailProvider(aws_access_key="", aws_secret_key="")
17-
return EmailService(email_provider)
19+
return EmailService(provider=get_email_service_provider())
1820

1921

2022
# TODO (Mayank, Nov 30th) - Remove test emails once email service is fully implemented
21-
@router.post("/send-test-email/")
23+
@router.post("/send-test")
2224
async def send_welcome_email(
2325
recipient: str,
2426
user_name: str,
2527
email_service: Annotated[IEmailService, Depends(get_email_service)],
2628
):
27-
email_service.send_welcome_email(recipient, user_name)
28-
return {"message": f"Welcome email sent to {user_name}!"}
29+
return email_service.send_email(
30+
templateType=EmailTemplateType.TEST,
31+
content=EmailContent[MockEmailData](
32+
recipient=recipient, data=MockEmailData(name=user_name, date="2021-12-01")
33+
),
34+
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import json
2+
from abc import ABC
3+
from dataclasses import dataclass
4+
from enum import Enum
5+
from typing import Generic, TypeVar
6+
7+
EmailData = TypeVar("EmailData")
8+
9+
10+
class TemplateData(ABC):
11+
def get_formatted_string(self) -> str:
12+
class_dict = self.__dict__
13+
try:
14+
formatted_string = json.dumps(class_dict) # Try to convert to a JSON string
15+
except (TypeError, ValueError) as e:
16+
raise Exception(f"Error converting class to JSON: {e}")
17+
18+
return formatted_string
19+
20+
21+
@dataclass
22+
class MockEmailData(TemplateData):
23+
name: str
24+
date: str
25+
26+
27+
class EmailTemplateType(Enum):
28+
TEST = "Test"
29+
30+
31+
@dataclass
32+
class EmailContent(Generic[EmailData]):
33+
recipient: str
34+
data: EmailData

backend/app/server.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@
55
from dotenv import load_dotenv
66
from fastapi import FastAPI
77

8-
from app.routes import email
8+
from . import models
9+
from .routes import send_email, user, schedule
10+
from .utilities.constants import LOGGER_NAME
11+
from .utilities.firebase_init import initialize_firebase
12+
from .utilities.ses.ses_init import ensure_ses_templates
913

1014
load_dotenv()
1115

12-
# we need to load env variables before initialization code runs
13-
from . import models # noqa: E402
14-
from .routes import user, schedule # noqa: E402
15-
from .utilities.firebase_init import initialize_firebase # noqa: E402
16-
17-
log = logging.getLogger("uvicorn")
16+
log = logging.getLogger(LOGGER_NAME("server"))
1817

1918

2019
@asynccontextmanager
2120
async def lifespan(_: FastAPI):
2221
log.info("Starting up...")
22+
ensure_ses_templates()
2323
models.run_migrations()
2424
initialize_firebase()
2525
yield
@@ -31,11 +31,11 @@ async def lifespan(_: FastAPI):
3131
app = FastAPI(lifespan=lifespan)
3232
app.include_router(user.router)
3333
app.include_router(schedule.router)
34-
app.include_router(email.router)
35-
34+
app.include_router(send_email.router)
3635

3736
@app.get("/")
3837
def read_root():
38+
log.info("Hello World")
3939
return {"Hello": "World"}
4040

4141

0 commit comments

Comments
 (0)