feat: 9.2. Реализация резолверов

This commit is contained in:
2026-10-12 16:58:30 +03:00
parent 0cf8565afc
commit df6b34206d
+130
View File
@@ -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