feat: 9.2. Реализация резолверов
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
from ariadne import MutationType, QueryType, convert_kwargs_to_snake_case
|
||||
from graphql import GraphQLError
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.audit.service import write_event
|
||||
from app.core.jwt import create_access_token
|
||||
from app.core.passwords import (
|
||||
WeakPasswordError,
|
||||
hash_password,
|
||||
validate_policy,
|
||||
)
|
||||
from app.models import AuditEvent, OAuthClient, User
|
||||
|
||||
# Создать объекты QueryType() и MutationType() из Ariadne
|
||||
# к ним будут привязываться резолверы через декораторы @query.field("...") и @mutation.field("...")
|
||||
query = QueryType()
|
||||
mutation = MutationType()
|
||||
|
||||
|
||||
# me – возвращает `info.context["user"]`;
|
||||
@query.field("me")
|
||||
def resolve_me(_, info: Any) -> Any:
|
||||
return info.context["user"]
|
||||
|
||||
|
||||
# читает из контекста db,
|
||||
# применяет поиск (ilike) и пагинацию (offset/limit),
|
||||
# возвращает список;
|
||||
@query.field("users")
|
||||
def resolve_users(
|
||||
_, info: Any, search: str | None = None, limit: int | None = None, offset: int = 0
|
||||
) -> list[User]:
|
||||
db = info.context["db"]
|
||||
stmt = select(User)
|
||||
if search:
|
||||
stmt = stmt.where(User.email.ilike(f"%{search}%"))
|
||||
stmt = stmt.order_by(User.id).offset(offset or 0)
|
||||
if limit:
|
||||
stmt = stmt.limit(limit)
|
||||
return list(db.scalars(stmt).all())
|
||||
|
||||
|
||||
# clients – возвращает всех клиентов;
|
||||
@query.field("clients")
|
||||
def resolve_clients(_, info: Any) -> list[OAuthClient]:
|
||||
db = info.context["db"]
|
||||
return list(db.scalars(select(OAuthClient).order_by(OAuthClient.id)).all())
|
||||
|
||||
|
||||
# auditEvents – фильтрует по eventType,
|
||||
# сортирует по createdAt desc, применяет пагинацию.
|
||||
@query.field("auditEvents")
|
||||
@convert_kwargs_to_snake_case
|
||||
def resolve_audit_events(
|
||||
_,
|
||||
info: Any,
|
||||
event_type: str | None = None,
|
||||
limit: int | None = None,
|
||||
offset: int = 0,
|
||||
) -> list[AuditEvent]:
|
||||
db = info.context["db"]
|
||||
stmt = select(AuditEvent)
|
||||
if event_type:
|
||||
stmt = stmt.where(AuditEvent.event_type == event_type)
|
||||
stmt = stmt.order_by(AuditEvent.created_at.desc()).offset(offset or 0)
|
||||
if limit:
|
||||
stmt = stmt.limit(limit)
|
||||
return list(db.scalars(stmt).all())
|
||||
|
||||
|
||||
# повторяет логику регистрации из REST-эндпоинта:
|
||||
# проверка email,
|
||||
# validate_policy,
|
||||
# hash_password,
|
||||
# запись аудита,
|
||||
# возврат пары токенов;
|
||||
@mutation.field("registerUser")
|
||||
@convert_kwargs_to_snake_case
|
||||
def resolve_register_user(_, info: Any, email: str, password: str) -> dict[str, str]:
|
||||
db = info.context["db"]
|
||||
if db.scalar(select(User).where(User.email == email)) is not None:
|
||||
raise GraphQLError("Email already registered")
|
||||
try:
|
||||
validate_policy(password, user_inputs=[email])
|
||||
except WeakPasswordError as exc:
|
||||
raise GraphQLError(str(exc)) from exc
|
||||
|
||||
user = User(email=email, hashed_password=hash_password(password))
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
write_event(db, "user.created", actor_id=user.id)
|
||||
return {"access_token": create_access_token(str(user.id)), "token_type": "bearer"}
|
||||
|
||||
|
||||
# генерирует случайный client_id (например, через uuid4),
|
||||
# сохраняет, пишет событие аудита;
|
||||
@mutation.field("createClient")
|
||||
@convert_kwargs_to_snake_case
|
||||
def resolve_create_client(
|
||||
_, info: Any, name: str, is_confidential: bool = True
|
||||
) -> OAuthClient:
|
||||
db = info.context["db"]
|
||||
client = OAuthClient(
|
||||
client_id=uuid4().hex, name=name, is_confidential=is_confidential
|
||||
)
|
||||
db.add(client)
|
||||
db.commit()
|
||||
db.refresh(client)
|
||||
write_event(db, "client.created", client_id=client.client_id)
|
||||
return client
|
||||
|
||||
|
||||
# ищет по id, ставит is_active = False, пишет аудит.
|
||||
@mutation.field("disableUser")
|
||||
@convert_kwargs_to_snake_case
|
||||
def resolve_disable_user(_, info: Any, user_id: int) -> User:
|
||||
db = info.context["db"]
|
||||
user = db.get(User, user_id)
|
||||
if user is None:
|
||||
raise GraphQLError("User not found")
|
||||
user.is_active = False
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
actor = info.context.get("user")
|
||||
write_event(db, "user.disabled", actor_id=actor.id if actor else None)
|
||||
return user
|
||||
Reference in New Issue
Block a user