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

218 lines
8.7 KiB
Python

from typing import cast
import strawberry
from strawberry.types import Info
from asgiref.sync import sync_to_async
from core.graphql.inputs.project_scope import (
ProjectScopeInput,
ProjectScopeUpdateInput,
ProjectScopeCategoryInput,
ProjectScopeCategoryUpdateInput,
ProjectScopeTaskInput,
ProjectScopeTaskUpdateInput,
CreateProjectScopeFromTemplateInput,
)
from core.graphql.types.project_scope import (
ProjectScopeType,
ProjectScopeCategoryType,
ProjectScopeTaskType,
)
from core.graphql.utils import create_object, update_object, delete_object
from core.models.account import Account, AccountAddress
from core.models.project import Project
from core.models.project_scope import ProjectScope, ProjectScopeCategory, ProjectScopeTask
from core.models.project_scope_template import ProjectScopeTemplate
from core.services.events import (
publish_project_scope_created, publish_project_scope_updated, publish_project_scope_deleted,
publish_project_scope_category_created, publish_project_scope_category_updated, publish_project_scope_category_deleted,
publish_project_scope_task_created, publish_project_scope_task_updated, publish_project_scope_task_deleted,
publish_project_scope_template_instantiated,
)
@strawberry.type
class Mutation:
# ProjectScope CRUD
@strawberry.mutation(description="Create a new ProjectScope")
async def create_project_scope(self, input: ProjectScopeInput, info: Info) -> ProjectScopeType:
instance = await create_object(input, ProjectScope)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_project_scope_created(
scope_id=str(instance.id),
project_id=str(instance.project_id),
triggered_by=profile
)
return cast(ProjectScopeType, instance)
@strawberry.mutation(description="Update an existing ProjectScope")
async def update_project_scope(self, input: ProjectScopeUpdateInput, info: Info) -> ProjectScopeType:
instance = await update_object(input, ProjectScope)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_project_scope_updated(
scope_id=str(instance.id),
project_id=str(instance.project_id),
triggered_by=profile
)
return cast(ProjectScopeType, instance)
@strawberry.mutation(description="Delete a ProjectScope")
async def delete_project_scope(self, id: strawberry.ID, info: Info) -> strawberry.ID:
instance = await delete_object(id, ProjectScope)
if not instance:
raise ValueError(f"ProjectScope with ID {id} does not exist")
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_project_scope_deleted(
scope_id=str(id),
project_id=str(instance.project_id),
triggered_by=profile
)
return id
@strawberry.mutation(description="Create a ProjectScopeCategory")
async def create_project_scope_category(self, input: ProjectScopeCategoryInput, info: Info) -> ProjectScopeCategoryType:
instance = await create_object(input, ProjectScopeCategory)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_project_scope_category_created(
category_id=str(instance.id),
scope_id=str(instance.scope_id),
triggered_by=profile
)
return cast(ProjectScopeCategoryType, instance)
@strawberry.mutation(description="Update a ProjectScopeCategory")
async def update_project_scope_category(self, input: ProjectScopeCategoryUpdateInput, info: Info) -> ProjectScopeCategoryType:
instance = await update_object(input, ProjectScopeCategory)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_project_scope_category_updated(
category_id=str(instance.id),
scope_id=str(instance.scope_id),
triggered_by=profile
)
return cast(ProjectScopeCategoryType, instance)
@strawberry.mutation(description="Delete a ProjectScopeCategory")
async def delete_project_scope_category(self, id: strawberry.ID, info: Info) -> strawberry.ID:
instance = await delete_object(id, ProjectScopeCategory)
if not instance:
raise ValueError(f"ProjectScopeCategory with ID {id} does not exist")
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_project_scope_category_deleted(
category_id=str(id),
scope_id=str(instance.scope_id),
triggered_by=profile
)
return id
@strawberry.mutation(description="Create a ProjectScopeTask")
async def create_project_scope_task(self, input: ProjectScopeTaskInput, info: Info) -> ProjectScopeTaskType:
instance = await create_object(input, ProjectScopeTask)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_project_scope_task_created(
task_id=str(instance.id),
category_id=str(instance.category_id),
triggered_by=profile
)
return cast(ProjectScopeTaskType, instance)
@strawberry.mutation(description="Update a ProjectScopeTask")
async def update_project_scope_task(self, input: ProjectScopeTaskUpdateInput, info: Info) -> ProjectScopeTaskType:
instance = await update_object(input, ProjectScopeTask)
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_project_scope_task_updated(
task_id=str(instance.id),
category_id=str(instance.category_id),
triggered_by=profile
)
return cast(ProjectScopeTaskType, instance)
@strawberry.mutation(description="Delete a ProjectScopeTask")
async def delete_project_scope_task(self, id: strawberry.ID, info: Info) -> strawberry.ID:
instance = await delete_object(id, ProjectScopeTask)
if not instance:
raise ValueError(f"ProjectScopeTask with ID {id} does not exist")
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_project_scope_task_deleted(
task_id=str(id),
category_id=str(instance.category_id),
triggered_by=profile
)
return id
@strawberry.mutation(description="Instantiate a ProjectScope (with Categories and Tasks) from a ProjectScopeTemplate")
async def create_project_scope_from_template(self, input: CreateProjectScopeFromTemplateInput, info: Info) -> ProjectScopeType:
def _do_create_sync() -> tuple[ProjectScope, str, str]:
# Load required objects synchronously (ORM-safe in this thread)
project = (
Project.objects
.select_related("account_address__account")
.get(pk=input.project_id.node_id)
)
tpl = ProjectScopeTemplate.objects.get(pk=input.template_id.node_id)
# Defaults derived from project (if project has an account_address)
account = None
account_address = None
if project.account_address_id:
account_address = project.account_address
account = account_address.account
if input.account_address_id:
account_address = AccountAddress.objects.get(pk=input.account_address_id.node_id)
account = account_address.account
if input.account_id:
account = Account.objects.get(pk=input.account_id.node_id)
# Instantiate the ProjectScope object from the template
instance = tpl.instantiate(
project=project,
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,
)
# Persist the relation on the project
project.scope = instance
project.save(update_fields=["scope"])
return instance, str(tpl.id), str(project.id)
instance, template_id, project_id = await sync_to_async(_do_create_sync, thread_sensitive=True)()
# Publish event
profile = getattr(info.context.request, 'profile', None)
await publish_project_scope_template_instantiated(
scope_id=str(instance.id),
template_id=template_id,
project_id=project_id,
triggered_by=profile
)
return cast(ProjectScopeType, instance)