From 7e296a247e6af4ee8af377b72eb87f69631a8e37 Mon Sep 17 00:00:00 2001 From: Benjamin Bolte Date: Mon, 10 Jun 2024 23:55:26 -0700 Subject: [PATCH] stuff is broken rn --- Makefile | 3 +- frontend/src/App.test.tsx | 8 --- linguaphoto/routers/users.py | 37 ------------- linguaphoto/utils/__init__.py | 0 linguaphoto/utils/email.py | 101 ---------------------------------- pyproject.toml | 1 + tests/conftest.py | 28 +--------- 7 files changed, 4 insertions(+), 174 deletions(-) delete mode 100644 frontend/src/App.test.tsx delete mode 100644 linguaphoto/utils/__init__.py delete mode 100644 linguaphoto/utils/email.py diff --git a/Makefile b/Makefile index 9b3657d..f32c618 100644 --- a/Makefile +++ b/Makefile @@ -80,6 +80,7 @@ test-backend: test-frontend: @cd frontend && npm run test -- --watchAll=false -test: test-backend test-frontend +# test: test-backend test-frontend +test: test-backend .PHONY: test diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx deleted file mode 100644 index ea3efa2..0000000 --- a/frontend/src/App.test.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import App from "App"; - -test("renders stompy urdf link", () => { - render(); - const linkElement = screen.getByText(/Robots/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/linguaphoto/routers/users.py b/linguaphoto/routers/users.py index df73eb0..6e97fa3 100644 --- a/linguaphoto/routers/users.py +++ b/linguaphoto/routers/users.py @@ -13,7 +13,6 @@ from linguaphoto.crypto import get_new_api_key, get_new_user_id from linguaphoto.db import Crud from linguaphoto.model import User -from linguaphoto.utils.email import OneTimePassPayload, send_delete_email, send_otp_email logger = logging.getLogger(__name__) @@ -64,23 +63,6 @@ def validate_email(email: str) -> str: return email -@users_router.post("/login") -async def login_user_endpoint(data: UserSignup) -> bool: - """Takes the user email and sends them a one-time login password. - - Args: - data: The payload with the user email and the login URL to redirect to - when the user logs in. - - Returns: - True if the email was sent successfully. - """ - email = validate_email(data.email) - payload = OneTimePassPayload(email, lifetime=data.lifetime) - await send_otp_email(payload, data.login_url) - return True - - class OneTimePass(BaseModel): payload: str @@ -118,24 +100,6 @@ async def get_login_response(email: str, lifetime: int, crud: Crud) -> UserLogin return UserLoginResponse(api_key=str(api_key)) -@users_router.post("/otp", response_model=UserLoginResponse) -async def otp_endpoint( - data: OneTimePass, - crud: Annotated[Crud, Depends(Crud.get)], -) -> UserLoginResponse: - """Takes the one-time password and returns an API key. - - Args: - data: The one-time password payload. - crud: The database CRUD object. - - Returns: - The API key if the one-time password is valid. - """ - payload = OneTimePassPayload.decode(data.payload) - return await get_login_response(payload.email, payload.lifetime, crud) - - async def get_google_user_info(token: str) -> dict: async with aiohttp.ClientSession() as session: response = await session.get( @@ -198,7 +162,6 @@ async def delete_user_endpoint( if user_obj is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") await crud.delete_user(user_obj) - await send_delete_email(user_obj.email) return True diff --git a/linguaphoto/utils/__init__.py b/linguaphoto/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/linguaphoto/utils/email.py b/linguaphoto/utils/email.py deleted file mode 100644 index 34fb060..0000000 --- a/linguaphoto/utils/email.py +++ /dev/null @@ -1,101 +0,0 @@ -"""Utility functions for sending emails to users.""" - -import argparse -import asyncio -import datetime -import logging -import textwrap -from dataclasses import dataclass -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText - -import aiosmtplib - -from linguaphoto.crypto import decode_jwt, encode_jwt -from linguaphoto.settings import settings - -logger = logging.getLogger(__name__) - - -async def send_email(subject: str, body: str, to: str) -> None: - msg = MIMEMultipart("alternative") - msg["Subject"] = subject - msg["From"] = f"{settings.email.sender_name} <{settings.email.sender_email}>" - msg["To"] = to - - msg.attach(MIMEText(body, "html")) - - smtp_client = aiosmtplib.SMTP(hostname=settings.email.host, port=settings.email.port) - - await smtp_client.connect() - await smtp_client.login(settings.email.username, settings.email.password) - await smtp_client.sendmail(settings.email.sender_email, to, msg.as_string()) - await smtp_client.quit() - - -@dataclass -class OneTimePassPayload: - email: str - lifetime: int - - def encode(self) -> str: - expire_minutes = settings.crypto.expire_otp_minutes - expire_after = datetime.timedelta(minutes=expire_minutes) - return encode_jwt({"email": self.email, "lifetime": self.lifetime}, expire_after=expire_after) - - @classmethod - def decode(cls, payload: str) -> "OneTimePassPayload": - data = decode_jwt(payload) - return cls(email=data["email"], lifetime=data["lifetime"]) - - -async def send_otp_email(payload: OneTimePassPayload, login_url: str) -> None: - url = f"{login_url}?otp={payload.encode()}" - - body = textwrap.dedent( - f""" -

K-Scale Labs

-

log in

-

Or copy-paste this link: {url}

- """ - ) - - await send_email(subject="One-Time Password", body=body, to=payload.email) - - -async def send_delete_email(email: str) -> None: - body = textwrap.dedent( - """ -

K-Scale Labs

-

your account has been deleted

- """ - ) - - await send_email(subject="Account Deleted", body=body, to=email) - - -async def send_waitlist_email(email: str) -> None: - body = textwrap.dedent( - """ -

K-Scale Labs

-

you're on the waitlist!

-

Thanks for signing up! We'll let you know when you can log in.

- """ - ) - - await send_email(subject="Waitlist", body=body, to=email) - - -def test_email_adhoc() -> None: - parser = argparse.ArgumentParser(description="Test sending an email.") - parser.add_argument("subject", help="The subject of the email.") - parser.add_argument("body", help="The body of the email.") - parser.add_argument("to", help="The recipient of the email.") - args = parser.parse_args() - - asyncio.run(send_email(args.subject, args.body, args.to)) - - -if __name__ == "__main__": - # python -m linguaphoto.utils.email - test_email_adhoc() diff --git a/pyproject.toml b/pyproject.toml index 236c917..1f4940b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ namespace_packages = false module = [ "boto3.*", "moto.*", + "openai.*", ] ignore_missing_imports = true diff --git a/tests/conftest.py b/tests/conftest.py index 275bc99..69bf1c2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,7 +9,7 @@ from fastapi.testclient import TestClient from moto.dynamodb import mock_dynamodb from moto.server import ThreadedMotoServer -from pytest_mock.plugin import MockerFixture, MockType +from pytest_mock.plugin import MockerFixture os.environ["LINGUAPHOTO_ENVIRONMENT"] = "local" @@ -75,29 +75,3 @@ def app_client() -> Generator[TestClient, None, None]: with TestClient(app) as app_client: yield app_client - - -@pytest.fixture(autouse=True) -def mock_send_email(mocker: MockerFixture) -> MockType: - mock = mocker.patch("linguaphoto.utils.email.send_email") - mock.return_value = None - return mock - - -@pytest.fixture() -def authenticated_user(app_client: TestClient) -> tuple[TestClient, str, str]: - from linguaphoto.utils.email import OneTimePassPayload - - test_email = "test@example.com" - - # Logs the user in using the OTP. - otp = OneTimePassPayload(email=test_email, lifetime=3600) - response = app_client.post("/users/otp", json={"payload": otp.encode()}) - assert response.status_code == 200, response.json() - - # Gets a session token. - response = app_client.post("/users/refresh") - data = response.json() - assert response.status_code == 200, data - - return app_client, test_email, data["token"]