feat(responses): support safe option editing
This commit is contained in:
@@ -732,6 +732,83 @@ def _lock_poll_for_response(session: Session, *, tenant_id: str, poll_id: str) -
|
||||
return poll
|
||||
|
||||
|
||||
def update_poll_option(
|
||||
session: Session,
|
||||
*,
|
||||
tenant_id: str,
|
||||
poll_id: str,
|
||||
option_id: str,
|
||||
label: str,
|
||||
description: str | None,
|
||||
value: dict[str, Any] | None,
|
||||
metadata: dict[str, Any],
|
||||
) -> PollOption:
|
||||
"""Update one option and invalidate only answers bound to that option.
|
||||
|
||||
The Poll row is locked first, matching response submission. The owning
|
||||
API commits the option and response JSON updates in the same transaction.
|
||||
"""
|
||||
|
||||
poll = _lock_poll_for_response(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
poll_id=poll_id,
|
||||
)
|
||||
if poll.status not in {"draft", "open"}:
|
||||
raise PollError("Only draft or open poll options can be edited")
|
||||
option = (
|
||||
session.query(PollOption)
|
||||
.filter(
|
||||
PollOption.tenant_id == tenant_id,
|
||||
PollOption.poll_id == poll.id,
|
||||
PollOption.id == option_id,
|
||||
PollOption.deleted_at.is_(None),
|
||||
)
|
||||
.populate_existing()
|
||||
.with_for_update()
|
||||
.one_or_none()
|
||||
)
|
||||
if option is None:
|
||||
raise PollError("Unknown poll option")
|
||||
normalized_value = dict(value) if value is not None else None
|
||||
normalized_metadata = dict(metadata)
|
||||
changed = (
|
||||
option.label != label
|
||||
or option.description != description
|
||||
or option.value != normalized_value
|
||||
or (option.metadata_ or {}) != normalized_metadata
|
||||
)
|
||||
if not changed:
|
||||
return option
|
||||
|
||||
responses = (
|
||||
session.query(PollResponse)
|
||||
.filter(
|
||||
PollResponse.tenant_id == tenant_id,
|
||||
PollResponse.poll_id == poll.id,
|
||||
PollResponse.deleted_at.is_(None),
|
||||
)
|
||||
.order_by(PollResponse.id.asc())
|
||||
.populate_existing()
|
||||
.with_for_update()
|
||||
.all()
|
||||
)
|
||||
option.label = label
|
||||
option.description = description
|
||||
option.value = normalized_value
|
||||
option.metadata_ = normalized_metadata
|
||||
for response in responses:
|
||||
retained_answers = [
|
||||
answer
|
||||
for answer in (response.answers or [])
|
||||
if not isinstance(answer, dict) or answer.get("option_id") != option.id
|
||||
]
|
||||
if retained_answers != (response.answers or []):
|
||||
response.answers = retained_answers
|
||||
session.flush()
|
||||
return option
|
||||
|
||||
|
||||
def submit_poll_response(session: Session, *, tenant_id: str, poll_id: str, payload: PollSubmitResponseRequest) -> PollResponse:
|
||||
poll = _lock_poll_for_response(
|
||||
session,
|
||||
|
||||
Reference in New Issue
Block a user