feat(api+testing): select member by email
- Reorganized folder structure so routes live in the same folder - Set up pytest fixtures for future use
This commit is contained in:
		@@ -4,8 +4,7 @@ import redis.asyncio as redis
 | 
			
		||||
from fastapi import FastAPI
 | 
			
		||||
from fastapi_limiter import FastAPILimiter
 | 
			
		||||
 | 
			
		||||
from src.neo_neo_todo.control import control
 | 
			
		||||
from src.neo_neo_todo.sessions import sessions
 | 
			
		||||
from src.neo_neo_todo.routes import control, sessions
 | 
			
		||||
from src.neo_neo_todo.utils.database import pool
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,10 @@
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
from psycopg import sql
 | 
			
		||||
from psycopg.rows import class_row
 | 
			
		||||
from psycopg_pool import AsyncConnectionPool
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dataclass
 | 
			
		||||
class Member:
 | 
			
		||||
@@ -9,3 +13,26 @@ class Member:
 | 
			
		||||
    password_hash: str
 | 
			
		||||
    created: datetime
 | 
			
		||||
    email_verified: datetime | None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def select_member_by_email(db: AsyncConnectionPool, email: str) -> Member | None:
 | 
			
		||||
    """
 | 
			
		||||
    Find exactly one email match in users list in PostgreSQL database
 | 
			
		||||
    """
 | 
			
		||||
    async with db.connection() as conn:
 | 
			
		||||
        async with conn.cursor(row_factory=class_row(Member)) as curr:
 | 
			
		||||
            query = sql.SQL(
 | 
			
		||||
                """
 | 
			
		||||
                    SELECT id, email, password_hash, created, email_verified
 | 
			
		||||
                        FROM members
 | 
			
		||||
                        WHERE LOWER(email) = LOWER(%s);
 | 
			
		||||
                    """
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            await curr.execute(query, (email,))
 | 
			
		||||
 | 
			
		||||
            print(query.as_string(conn))
 | 
			
		||||
 | 
			
		||||
            member = await curr.fetchone()
 | 
			
		||||
 | 
			
		||||
            return None if member is None else member
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
from fastapi import APIRouter
 | 
			
		||||
from starlette.requests import Request
 | 
			
		||||
 | 
			
		||||
router = APIRouter(
 | 
			
		||||
    prefix="/control",
 | 
			
		||||
@@ -8,5 +7,5 @@ router = APIRouter(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@router.get("/ping")
 | 
			
		||||
async def ping(request: Request):
 | 
			
		||||
async def ping():
 | 
			
		||||
    return {"ping": "pong"}
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
from fastapi import APIRouter, Depends
 | 
			
		||||
from fastapi_limiter.depends import RateLimiter
 | 
			
		||||
from starlette.requests import Request
 | 
			
		||||
 | 
			
		||||
from src.neo_neo_todo.models.session import LoginData
 | 
			
		||||
 | 
			
		||||
@@ -11,7 +10,7 @@ router = APIRouter(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@router.post("", dependencies=[Depends(RateLimiter(times=100, seconds=600))])
 | 
			
		||||
async def login(request: Request, login_data: LoginData):
 | 
			
		||||
async def login(login_data: LoginData):
 | 
			
		||||
    """
 | 
			
		||||
    Login to the todo app
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user