mirror of
https://github.com/lorsanstand/Aether.git
synced 2026-09-18 14:18:20 +03:00
refractor structure project and add user endpoint
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
from celery import Celery
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
celery_app = Celery(
|
||||
"app.core.celery_app",
|
||||
broker=settings.RABBITMQ_URL,
|
||||
backend="rpc://"
|
||||
)
|
||||
|
||||
celery_app.autodiscover_tasks(["app.tasks"])
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
from typing import Literal, List
|
||||
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
COMPANY_NAME: str
|
||||
|
||||
MODE: Literal["DEV", "TEST", "PROD"]
|
||||
LOG_LEVEL: Literal["ERROR", "WARNING", "INFO", "DEBUG"]
|
||||
|
||||
HOST: str
|
||||
PORT: int
|
||||
WORKERS: int
|
||||
URL: str
|
||||
|
||||
CORS_ORIGINS: List[str] = ["http://localhost:5500", "http://127.0.0.1:5500", "http://localhost:8080", "http://127.0.0.1:8080", "null"]
|
||||
CORS_HEADERS: List[str] = ["*"]
|
||||
CORS_METHODS: List[str] = ["*"]
|
||||
|
||||
SECRET_KEY: str
|
||||
ALGORITHM: str
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 15
|
||||
EMAIL_TOKEN_EXPIRE_MINUTES: int = 60
|
||||
REFRESH_TOKEN_EXPIRE_DAYS: int = 30
|
||||
|
||||
SMTP_SERVER: str
|
||||
SMTP_PORT: int
|
||||
SMTP_EMAIL: str
|
||||
SMTP_PASS: str
|
||||
|
||||
DB_HOST: str
|
||||
DB_PORT: int
|
||||
DB_PASS: str
|
||||
DB_USER: str
|
||||
DB_NAME: str
|
||||
|
||||
@property
|
||||
def DATABASE_URL(self):
|
||||
return f"postgresql+asyncpg://{self.DB_USER}:{self.DB_PASS}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
|
||||
|
||||
REDIS_HOST: str = "localhost"
|
||||
REDIS_PORT: int = 6397
|
||||
REDIS_PASS: str = ""
|
||||
REDIS_DB: int = 0
|
||||
|
||||
@property
|
||||
def REDIS_URL(self):
|
||||
return f"redis://{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
|
||||
|
||||
RMQ_HOST: str
|
||||
RMQ_USER: str
|
||||
RMQ_PASS: str
|
||||
RMQ_PORT: int
|
||||
|
||||
@property
|
||||
def RABBITMQ_URL(self) -> str:
|
||||
return f"amqp://{self.RMQ_USER}:{self.RMQ_PASS}@{self.RMQ_HOST}:{self.RMQ_PORT}//"
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env", extra="allow")
|
||||
|
||||
|
||||
settings: Settings = Settings()
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
from enum import Enum
|
||||
|
||||
DB_NAMING_CONVENTION = {
|
||||
"ix": "%(column_0_label)s_idx",
|
||||
"uq": "%(table_name)s_%(column_0_name)s_key",
|
||||
"ck": "%(table_name)s_%(constraint_name)s_check",
|
||||
"fk": "%(table_name)s_%(column_0_name)s_fkey",
|
||||
"pk": "%(table_name)s_pkey",
|
||||
}
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
from typing import TypeVar, Generic, Optional, List, Union, Dict, Any
|
||||
import logging
|
||||
|
||||
from sqlalchemy import delete, insert, select, update, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
ModelType = TypeVar("ModelType", bound=Base)
|
||||
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
|
||||
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)
|
||||
|
||||
class BaseDAO(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
||||
model = None
|
||||
|
||||
@classmethod
|
||||
async def find_one_or_none(cls, session: AsyncSession, *filter, **filter_by) -> Optional[ModelType]:
|
||||
stmt = select(cls.model).filter(*filter).filter_by(**filter_by)
|
||||
result = await session.execute(stmt)
|
||||
return result.scalars().one_or_none()
|
||||
|
||||
|
||||
@classmethod
|
||||
async def find_all(
|
||||
cls,
|
||||
session: AsyncSession,
|
||||
offset: Optional[int],
|
||||
limit: Optional[int],
|
||||
*filter,
|
||||
**filter_by
|
||||
) -> List[ModelType]:
|
||||
stmt = select(cls.model).filter(*filter).filter_by(**filter_by)
|
||||
|
||||
if offset is not None:
|
||||
stmt = stmt.offset(offset)
|
||||
if limit is not None:
|
||||
stmt = stmt.limit(limit)
|
||||
|
||||
result = await session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
@classmethod
|
||||
async def add(
|
||||
cls,
|
||||
session: AsyncSession,
|
||||
obj_in: Union[CreateSchemaType, Dict[str, Any]]
|
||||
) -> Optional[ModelType]:
|
||||
if isinstance(obj_in, dict):
|
||||
create_data = obj_in
|
||||
else:
|
||||
create_data = obj_in.model_dump(exclude_unset=True)
|
||||
|
||||
try:
|
||||
stmt = insert(cls.model).values(**create_data).returning(cls.model)
|
||||
result = await session.execute(stmt)
|
||||
return result.scalars().first()
|
||||
except (SQLAlchemyError, Exception) as ex:
|
||||
if isinstance(ex, SQLAlchemyError):
|
||||
msg = "Database Exc: Cannot insert data into table"
|
||||
elif isinstance(ex, Exception):
|
||||
msg = "Unknown Exc: Cannot insert data into table"
|
||||
|
||||
log.error(msg, extra={"table": cls.model.__tablename__}, exc_info=True)
|
||||
|
||||
|
||||
@classmethod
|
||||
async def delete(cls, session: AsyncSession, *filter, **filter_by) -> None:
|
||||
stmt = delete(cls.model).filter(*filter).filter_by(**filter_by)
|
||||
await session.execute(stmt)
|
||||
|
||||
|
||||
@classmethod
|
||||
async def update(
|
||||
cls,
|
||||
session: AsyncSession,
|
||||
*where,
|
||||
obj_in: Union[UpdateSchemaType, Dict[str, Any]]
|
||||
) -> Optional[ModelType]:
|
||||
if isinstance(obj_in, Dict):
|
||||
update_data = obj_in
|
||||
else:
|
||||
update_data = obj_in.model_dump(exclude_unset=True)
|
||||
|
||||
stmt = update(cls.model).where(*where).values(update_data).returning(cls.model)
|
||||
|
||||
result = await session.execute(stmt)
|
||||
return result.scalars().one()
|
||||
|
||||
|
||||
@classmethod
|
||||
async def add_bulk(cls, session: AsyncSession, data: List[Dict[str, Any]]):
|
||||
try:
|
||||
result = await session.execute(
|
||||
insert(cls.model).returning(cls.model),
|
||||
data
|
||||
)
|
||||
return result.scalars().all()
|
||||
except (SQLAlchemyError, Exception) as e:
|
||||
if isinstance(e, SQLAlchemyError):
|
||||
msg = "Database Exc"
|
||||
elif isinstance(e, Exception):
|
||||
msg = "Unknown Exc"
|
||||
msg += ": Cannot bulk insert data into table"
|
||||
|
||||
log.error(msg, extra={"table": cls.model.__tablename__}, exc_info=True)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def update_bulk(cls, session: AsyncSession, data: List[Dict[str, Any]]):
|
||||
try:
|
||||
stmt = update(cls.model)
|
||||
await session.execute(update(cls.model), data)
|
||||
except (SQLAlchemyError, Exception) as e:
|
||||
if isinstance(e, SQLAlchemyError):
|
||||
msg = "Database Exc"
|
||||
elif isinstance(e, Exception):
|
||||
msg = "Unknown Exc"
|
||||
msg += ": Cannot bulk update data into table"
|
||||
|
||||
log.error(msg, extra={"table": cls.model.__tablename__}, exc_info=True)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def count(cls, session: AsyncSession, *filter, **filter_by):
|
||||
stmt = select(func.count()).select_from(
|
||||
cls.model).filter(*filter).filter_by(**filter_by)
|
||||
result = await session.execute(stmt)
|
||||
return result.scalar()
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||
from sqlalchemy import MetaData, NullPool, func
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.constants import DB_NAMING_CONVENTION
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
metadata = MetaData(naming_convention=DB_NAMING_CONVENTION)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
if settings.MODE == "TEST":
|
||||
DATABASE_URL = settings.TEST_DATABASE_URL
|
||||
DATABASE_PARAMS = {"poolclass": NullPool}
|
||||
else:
|
||||
DATABASE_URL = settings.DATABASE_URL
|
||||
DATABASE_PARAMS = {}
|
||||
|
||||
async_engine = create_async_engine(DATABASE_URL, **DATABASE_PARAMS)
|
||||
async_session_maker = async_sessionmaker(async_engine, expire_on_commit=False)
|
||||
@@ -0,0 +1,16 @@
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
|
||||
class InvalidTokenException(HTTPException):
|
||||
def __init__(self):
|
||||
super().__init__(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
|
||||
|
||||
|
||||
class TokenExpiredException(HTTPException):
|
||||
def __init__(self):
|
||||
super().__init__(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token has expired")
|
||||
|
||||
|
||||
class InvalidCredentialsException(HTTPException):
|
||||
def __init__(self):
|
||||
super().__init__(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid username or password")
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
from logging.config import dictConfig
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
LOGGING_CONFIG = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
|
||||
"formatters": {
|
||||
"colored": {
|
||||
"()": "colorlog.ColoredFormatter",
|
||||
"format": "[%(asctime)s] %(log_color)s%(levelname)s%(reset)s:"
|
||||
" (%(module)s) %(message)s",
|
||||
"log_colors": {
|
||||
"DEBUG": "cyan",
|
||||
"INFO": "green",
|
||||
"WARNING": "yellow",
|
||||
"ERROR": "red",
|
||||
"CRITICAL": "purple",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "colored",
|
||||
},
|
||||
},
|
||||
|
||||
"root": {
|
||||
"level": settings.LOG_LEVEL,
|
||||
"handlers": ["console"],
|
||||
},
|
||||
}
|
||||
|
||||
def set_logging():
|
||||
dictConfig(LOGGING_CONFIG)
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
from redis.asyncio import Redis, from_url
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
redis_client: Redis = None
|
||||
|
||||
async def init_redis() -> None:
|
||||
global redis_client
|
||||
redis_client = await from_url(
|
||||
settings.REDIS_URL,
|
||||
encoding="utf-8",
|
||||
decode_responses=True
|
||||
)
|
||||
|
||||
|
||||
async def close_redis() -> None:
|
||||
if redis_client:
|
||||
await redis_client.close()
|
||||
|
||||
|
||||
async def get_redis() -> Redis:
|
||||
return redis_client
|
||||
Reference in New Issue
Block a user