Files
scripts-2/app/graphql/resolvers.py
T

131 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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