feat(poll): expose the scheduling provider
This commit is contained in:
263
src/govoplan_poll/backend/capabilities.py
Normal file
263
src/govoplan_poll/backend/capabilities.py
Normal file
@@ -0,0 +1,263 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
|
||||
from govoplan_core.core.poll import (
|
||||
PollCapabilityError,
|
||||
PollCreateCommand,
|
||||
PollInvitationCommand,
|
||||
PollInvitationRef,
|
||||
PollOptionRef,
|
||||
PollRef,
|
||||
PollResponseRef,
|
||||
PollSchedulingProvider,
|
||||
PollSubmitResponseCommand,
|
||||
PollUpdateCommand,
|
||||
)
|
||||
from govoplan_poll.backend.schemas import (
|
||||
PollAnswerInput,
|
||||
PollCreateRequest,
|
||||
PollDecisionRequest,
|
||||
PollInvitationCreateRequest,
|
||||
PollOptionInput,
|
||||
PollSubmitResponseRequest,
|
||||
)
|
||||
from govoplan_poll.backend.service import (
|
||||
PollError,
|
||||
close_poll,
|
||||
create_poll,
|
||||
create_poll_invitation,
|
||||
decide_poll,
|
||||
get_poll,
|
||||
list_poll_responses,
|
||||
open_poll,
|
||||
poll_result_summary_by_id,
|
||||
submit_poll_response,
|
||||
)
|
||||
|
||||
|
||||
def _poll_ref(poll: object) -> PollRef:
|
||||
return PollRef(
|
||||
id=poll.id,
|
||||
status=poll.status,
|
||||
options=tuple(PollOptionRef(id=option.id, position=option.position) for option in poll.options),
|
||||
)
|
||||
|
||||
|
||||
def _response_invitation_id(response: object) -> str | None:
|
||||
value = (response.metadata_ or {}).get("invitation_id")
|
||||
return value if isinstance(value, str) else None
|
||||
|
||||
|
||||
class SqlPollSchedulingProvider(PollSchedulingProvider):
|
||||
def create_poll(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
tenant_id: str,
|
||||
user_id: str | None,
|
||||
command: PollCreateCommand,
|
||||
) -> PollRef:
|
||||
payload = PollCreateRequest(
|
||||
title=command.title,
|
||||
description=command.description,
|
||||
kind=command.kind,
|
||||
status=command.status,
|
||||
visibility=command.visibility,
|
||||
result_visibility=command.result_visibility,
|
||||
context_module=command.context_module,
|
||||
context_resource_type=command.context_resource_type,
|
||||
context_resource_id=command.context_resource_id,
|
||||
workflow_state=command.workflow_state,
|
||||
workflow_steps=[dict(step) for step in command.workflow_steps],
|
||||
allow_anonymous=command.allow_anonymous,
|
||||
allow_response_update=command.allow_response_update,
|
||||
min_choices=command.min_choices,
|
||||
max_choices=command.max_choices,
|
||||
opens_at=command.opens_at,
|
||||
closes_at=command.closes_at,
|
||||
options=[
|
||||
PollOptionInput(
|
||||
key=option.key,
|
||||
label=option.label,
|
||||
description=option.description,
|
||||
value=dict(option.value) if option.value is not None else None,
|
||||
metadata=dict(option.metadata),
|
||||
)
|
||||
for option in command.options
|
||||
],
|
||||
metadata=dict(command.metadata),
|
||||
)
|
||||
try:
|
||||
poll = create_poll(session, tenant_id=tenant_id, user_id=user_id, payload=payload)
|
||||
except PollError as exc:
|
||||
raise PollCapabilityError(str(exc)) from exc
|
||||
return _poll_ref(poll)
|
||||
|
||||
def create_invitation(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
tenant_id: str,
|
||||
poll_id: str,
|
||||
command: PollInvitationCommand,
|
||||
) -> PollInvitationRef:
|
||||
payload = PollInvitationCreateRequest(
|
||||
respondent_id=command.respondent_id,
|
||||
respondent_label=command.respondent_label,
|
||||
email=command.email,
|
||||
expires_at=command.expires_at,
|
||||
metadata=dict(command.metadata),
|
||||
)
|
||||
try:
|
||||
invitation, token = create_poll_invitation(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
poll_id=poll_id,
|
||||
payload=payload,
|
||||
)
|
||||
except PollError as exc:
|
||||
raise PollCapabilityError(str(exc)) from exc
|
||||
return PollInvitationRef(id=invitation.id, token=token)
|
||||
|
||||
def submit_response(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
tenant_id: str,
|
||||
poll_id: str,
|
||||
command: PollSubmitResponseCommand,
|
||||
) -> PollResponseRef:
|
||||
payload = PollSubmitResponseRequest(
|
||||
respondent_id=command.respondent_id,
|
||||
respondent_label=command.respondent_label,
|
||||
answers=[
|
||||
PollAnswerInput(
|
||||
option_id=answer.option_id,
|
||||
option_key=answer.option_key,
|
||||
value=answer.value,
|
||||
rank=answer.rank,
|
||||
)
|
||||
for answer in command.answers
|
||||
],
|
||||
metadata=dict(command.metadata),
|
||||
)
|
||||
try:
|
||||
response = submit_poll_response(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
poll_id=poll_id,
|
||||
payload=payload,
|
||||
)
|
||||
except PollError as exc:
|
||||
raise PollCapabilityError(str(exc)) from exc
|
||||
return PollResponseRef(
|
||||
invitation_id=_response_invitation_id(response),
|
||||
submitted_at=response.submitted_at,
|
||||
respondent_id=response.respondent_id,
|
||||
)
|
||||
|
||||
def update_poll(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
tenant_id: str,
|
||||
poll_id: str,
|
||||
command: PollUpdateCommand,
|
||||
) -> PollRef:
|
||||
try:
|
||||
poll = get_poll(session, tenant_id=tenant_id, poll_id=poll_id)
|
||||
except PollError as exc:
|
||||
raise PollCapabilityError(str(exc)) from exc
|
||||
poll.title = command.title
|
||||
poll.description = command.description
|
||||
poll.visibility = command.visibility
|
||||
poll.result_visibility = command.result_visibility
|
||||
poll.allow_anonymous = command.allow_anonymous
|
||||
poll.allow_response_update = command.allow_response_update
|
||||
poll.closes_at = command.closes_at
|
||||
session.flush()
|
||||
return _poll_ref(poll)
|
||||
|
||||
def open_poll(self, session: object, *, tenant_id: str, poll_id: str) -> PollRef:
|
||||
return self._transition(open_poll, session, tenant_id=tenant_id, poll_id=poll_id)
|
||||
|
||||
def close_poll(self, session: object, *, tenant_id: str, poll_id: str) -> PollRef:
|
||||
return self._transition(close_poll, session, tenant_id=tenant_id, poll_id=poll_id)
|
||||
|
||||
def decide_poll(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
tenant_id: str,
|
||||
poll_id: str,
|
||||
option_id: str | None,
|
||||
) -> PollRef:
|
||||
try:
|
||||
poll = decide_poll(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
poll_id=poll_id,
|
||||
payload=PollDecisionRequest(option_id=option_id),
|
||||
)
|
||||
except PollError as exc:
|
||||
raise PollCapabilityError(str(exc)) from exc
|
||||
return _poll_ref(poll)
|
||||
|
||||
def get_poll(self, session: object, *, tenant_id: str, poll_id: str) -> PollRef:
|
||||
try:
|
||||
return _poll_ref(get_poll(session, tenant_id=tenant_id, poll_id=poll_id))
|
||||
except PollError as exc:
|
||||
raise PollCapabilityError(str(exc)) from exc
|
||||
|
||||
def set_workflow_context(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
tenant_id: str,
|
||||
poll_id: str,
|
||||
workflow_state: str,
|
||||
workflow_steps: Sequence[Mapping[str, object]],
|
||||
context_module: str,
|
||||
context_resource_type: str,
|
||||
context_resource_id: str,
|
||||
) -> PollRef:
|
||||
try:
|
||||
poll = get_poll(session, tenant_id=tenant_id, poll_id=poll_id)
|
||||
except PollError as exc:
|
||||
raise PollCapabilityError(str(exc)) from exc
|
||||
poll.workflow_state = workflow_state
|
||||
poll.workflow_steps = [dict(step) for step in workflow_steps]
|
||||
poll.context_module = context_module
|
||||
poll.context_resource_type = context_resource_type
|
||||
poll.context_resource_id = context_resource_id
|
||||
session.flush()
|
||||
return _poll_ref(poll)
|
||||
|
||||
def result_summary(self, session: object, *, tenant_id: str, poll_id: str) -> Mapping[str, object]:
|
||||
try:
|
||||
return poll_result_summary_by_id(session, tenant_id=tenant_id, poll_id=poll_id)
|
||||
except PollError as exc:
|
||||
raise PollCapabilityError(str(exc)) from exc
|
||||
|
||||
def list_responses(self, session: object, *, tenant_id: str, poll_id: str) -> tuple[PollResponseRef, ...]:
|
||||
try:
|
||||
responses = list_poll_responses(session, tenant_id=tenant_id, poll_id=poll_id)
|
||||
except PollError as exc:
|
||||
raise PollCapabilityError(str(exc)) from exc
|
||||
return tuple(
|
||||
PollResponseRef(
|
||||
invitation_id=_response_invitation_id(response),
|
||||
submitted_at=response.submitted_at,
|
||||
respondent_id=response.respondent_id,
|
||||
)
|
||||
for response in responses
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _transition(callback, session: object, *, tenant_id: str, poll_id: str) -> PollRef:
|
||||
try:
|
||||
poll = callback(session, tenant_id=tenant_id, poll_id=poll_id)
|
||||
except PollError as exc:
|
||||
raise PollCapabilityError(str(exc)) from exc
|
||||
return _poll_ref(poll)
|
||||
@@ -14,6 +14,7 @@ from govoplan_core.core.modules import (
|
||||
RoleTemplate,
|
||||
)
|
||||
from govoplan_core.db.base import Base
|
||||
from govoplan_core.core.poll import CAPABILITY_POLL_SCHEDULING
|
||||
from govoplan_poll.backend.db import models as poll_models # noqa: F401 - populate Poll ORM metadata
|
||||
|
||||
MODULE_ID = "poll"
|
||||
@@ -99,6 +100,13 @@ def _poll_router(_context: ModuleContext):
|
||||
return router
|
||||
|
||||
|
||||
def _poll_scheduling_provider(context: ModuleContext) -> object:
|
||||
del context
|
||||
from govoplan_poll.backend.capabilities import SqlPollSchedulingProvider
|
||||
|
||||
return SqlPollSchedulingProvider()
|
||||
|
||||
|
||||
manifest = ModuleManifest(
|
||||
id=MODULE_ID,
|
||||
name=MODULE_NAME,
|
||||
@@ -117,6 +125,7 @@ manifest = ModuleManifest(
|
||||
role_templates=ROLE_TEMPLATES,
|
||||
route_factory=_poll_router,
|
||||
tenant_summary_providers=(_tenant_summary,),
|
||||
capability_factories={CAPABILITY_POLL_SCHEDULING: _poll_scheduling_provider},
|
||||
migration_spec=MigrationSpec(
|
||||
module_id=MODULE_ID,
|
||||
metadata=Base.metadata,
|
||||
|
||||
Reference in New Issue
Block a user