nexus-5/core/graphql/mutations/scope_template.py
2026-01-26 11:09:40 -05:00

247 lines
9.8 KiB
Python

from typing import cast
import strawberry
from strawberry.types import Info
from asgiref.sync import sync_to_async
from core.graphql.pubsub import pubsub
from core.graphql.utils import create_object, update_object, delete_object, _decode_global_id
from core.graphql.types.scope_template import (
ScopeTemplateType,
AreaTemplateType,
TaskTemplateType,
)
from core.graphql.types.scope import ScopeType
from core.graphql.inputs.scope_template import (
ScopeTemplateInput, ScopeTemplateUpdateInput,
AreaTemplateInput, AreaTemplateUpdateInput,
TaskTemplateInput, TaskTemplateUpdateInput,
CreateScopeFromTemplateInput,
)
from core.models.scope_template import ScopeTemplate, AreaTemplate, TaskTemplate
from core.models.account import Account, AccountAddress
from strawberry.scalars import JSON
from core.services import build_scope_template
from core.services.events import (
publish_scope_template_created, publish_scope_template_updated, publish_scope_template_deleted,
publish_scope_template_instantiated,
publish_area_template_created, publish_area_template_updated, publish_area_template_deleted,
publish_task_template_created, publish_task_template_updated, publish_task_template_deleted,
)
@strawberry.type
class Mutation:
@strawberry.mutation(description="Create a new scope template")
async def create_scope_template(self, input: ScopeTemplateInput, info: Info) -> ScopeTemplateType:
instance = await create_object(input, ScopeTemplate)
await pubsub.publish("scope_template_created", instance.id)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_scope_template_created(
template_id=str(instance.id),
triggered_by=profile
)
return cast(ScopeTemplateType, instance)
@strawberry.mutation(description="Update an existing scope template")
async def update_scope_template(self, input: ScopeTemplateUpdateInput, info: Info) -> ScopeTemplateType:
instance = await update_object(input, ScopeTemplate)
await pubsub.publish("scope_template_updated", instance.id)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_scope_template_updated(
template_id=str(instance.id),
triggered_by=profile
)
return cast(ScopeTemplateType, instance)
@strawberry.mutation(description="Delete an existing scope template")
async def delete_scope_template(self, id: strawberry.ID, info: Info) -> strawberry.ID:
instance = await delete_object(id, ScopeTemplate)
if not instance:
raise ValueError(f"ScopeTemplate with ID {id} does not exist")
await pubsub.publish("scope_template_deleted", id)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_scope_template_deleted(
template_id=str(id),
triggered_by=profile
)
return id
@strawberry.mutation(description="Create a new area template")
async def create_area_template(self, input: AreaTemplateInput, info: Info) -> AreaTemplateType:
instance = await create_object(input, AreaTemplate)
await pubsub.publish("area_template_created", instance.id)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_area_template_created(
template_id=str(instance.id),
scope_template_id=str(instance.scope_template_id),
triggered_by=profile
)
return cast(AreaTemplateType, instance)
@strawberry.mutation(description="Update an existing area template")
async def update_area_template(self, input: AreaTemplateUpdateInput, info: Info) -> AreaTemplateType:
instance = await update_object(input, AreaTemplate)
await pubsub.publish("area_template_updated", instance.id)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_area_template_updated(
template_id=str(instance.id),
scope_template_id=str(instance.scope_template_id),
triggered_by=profile
)
return cast(AreaTemplateType, instance)
@strawberry.mutation(description="Delete an existing area template")
async def delete_area_template(self, id: strawberry.ID, info: Info) -> strawberry.ID:
instance = await delete_object(id, AreaTemplate)
if not instance:
raise ValueError(f"AreaTemplate with ID {id} does not exist")
await pubsub.publish("area_template_deleted", id)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_area_template_deleted(
template_id=str(id),
scope_template_id=str(instance.scope_template_id),
triggered_by=profile
)
return id
@strawberry.mutation(description="Create a new task template")
async def create_task_template(self, input: TaskTemplateInput, info: Info) -> TaskTemplateType:
instance = await create_object(input, TaskTemplate)
await pubsub.publish("task_template_created", instance.id)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_task_template_created(
template_id=str(instance.id),
area_template_id=str(instance.area_template_id),
triggered_by=profile
)
return cast(TaskTemplateType, instance)
@strawberry.mutation(description="Update an existing task template")
async def update_task_template(self, input: TaskTemplateUpdateInput, info: Info) -> TaskTemplateType:
instance = await update_object(input, TaskTemplate)
await pubsub.publish("task_template_updated", instance.id)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_task_template_updated(
template_id=str(instance.id),
area_template_id=str(instance.area_template_id),
triggered_by=profile
)
return cast(TaskTemplateType, instance)
@strawberry.mutation(description="Delete an existing task template")
async def delete_task_template(self, id: strawberry.ID, info: Info) -> strawberry.ID:
instance = await delete_object(id, TaskTemplate)
if not instance:
raise ValueError(f"TaskTemplate with ID {id} does not exist")
await pubsub.publish("task_template_deleted", id)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_task_template_deleted(
template_id=str(id),
area_template_id=str(instance.area_template_id),
triggered_by=profile
)
return id
@strawberry.mutation(description="Instantiate a Scope (with Areas and Tasks) from a ScopeTemplate")
async def create_scope_from_template(self, input: CreateScopeFromTemplateInput, info: Info) -> ScopeType:
def _do_create_sync():
template = ScopeTemplate.objects.get(pk=_decode_global_id(input.template_id))
account = Account.objects.get(pk=_decode_global_id(input.account_id))
account_address = None
if input.account_address_id:
account_address = AccountAddress.objects.get(
pk=_decode_global_id(input.account_address_id), account=account
)
scope = template.instantiate(
account=account,
account_address=account_address,
name=input.name,
description=input.description,
is_active=input.is_active if input.is_active is not None else True,
)
return scope, str(template.id), str(account.id)
# Run ORM-heavy work in a thread
instance, template_id, account_id = await sync_to_async(_do_create_sync)()
await pubsub.publish("scope_created_from_template", instance.id)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_scope_template_instantiated(
scope_id=str(instance.id),
template_id=template_id,
account_id=account_id,
triggered_by=profile
)
return cast(ScopeType, instance)
@strawberry.mutation(description="Create a ScopeTemplate (and nested Areas/Tasks) from a JSON payload")
async def create_scope_template_from_json(
self,
payload: JSON,
replace: bool = False,
info: Info | None = None,
) -> ScopeTemplateType:
"""
Accepts a JSON object matching the builder payload shape.
If replace=True and a template with the same name exists, it will be deleted first.
"""
def _do_create_sync():
if not isinstance(payload, dict):
raise ValueError("payload must be a JSON object")
name = payload.get("name")
if not name or not isinstance(name, str):
raise ValueError("payload.name is required and must be a string")
if replace:
ScopeTemplate.objects.filter(name=name).delete()
elif ScopeTemplate.objects.filter(name=name).exists():
raise ValueError(
f"A ScopeTemplate named '{name}' already exists (use replace=true to overwrite)"
)
tpl = build_scope_template(payload)
return tpl
instance = await sync_to_async(_do_create_sync)()
await pubsub.publish("scope_template_created", instance.id)
# Publish event
if info:
profile = getattr(info.context.request, 'profile', None)
await publish_scope_template_created(
template_id=str(instance.id),
triggered_by=profile
)
return cast(ScopeTemplateType, instance)