Co-authored-by: dev_agent <17+dev_agent@code.cubecraftcreations.com> Co-committed-by: dev_agent <17+dev_agent@code.cubecraftcreations.com>
This commit was merged in pull request #197.
This commit is contained in:
@@ -796,8 +796,9 @@ class QuestDefinitionsDao:
|
||||
rule: ScheduleRuleStorage | object = _UNSET,
|
||||
windows: list[tuple[str, str | None]] | object = _UNSET,
|
||||
skip_on_away: bool | object = _UNSET,
|
||||
assignee_child_ids: list[int] | object = _UNSET,
|
||||
) -> QuestDefinitionSnapshot:
|
||||
"""Edit a definition's metadata, rule and windows atomically.
|
||||
"""Edit a definition's metadata, rule, windows and assignees atomically.
|
||||
|
||||
Mirrors :meth:`create_with_rule_and_windows`: everything runs
|
||||
inside ONE transaction under the connection lock, and the
|
||||
@@ -818,11 +819,15 @@ class QuestDefinitionsDao:
|
||||
are deleted and the given set re-inserted, so missing windows
|
||||
are added, changed due times updated and removed windows
|
||||
dropped.
|
||||
- ``assignee_child_ids`` REPLACES the whole assignee set: every
|
||||
id is validated as existing-and-active BEFORE any write (the
|
||||
same check :meth:`add_assignee` applies), then links for
|
||||
children no longer listed are removed and links for newly
|
||||
listed children inserted; unchanged links are left alone.
|
||||
|
||||
Assignment is deliberately not settable here (multi-assignee is
|
||||
managed by :meth:`add_assignee`/:meth:`remove_assignee`), and
|
||||
activation is untouched. Raises ValueError when the definition
|
||||
does not exist. Edits change future instances only: no
|
||||
Activation is untouched. Raises ValueError when the definition
|
||||
does not exist or an assignee is rejected; either way the
|
||||
transaction rolls back and nothing is written. Edits change future instances only: no
|
||||
``quest_instances`` or ``completion_events`` row is touched.
|
||||
"""
|
||||
if skip_on_away is not _UNSET:
|
||||
@@ -834,6 +839,9 @@ class QuestDefinitionsDao:
|
||||
raise ValueError(
|
||||
f"quest definition {definition_id} does not exist"
|
||||
)
|
||||
if assignee_child_ids is not _UNSET:
|
||||
for child_id in assignee_child_ids:
|
||||
await self._validate_assignable_child(child_id)
|
||||
assignments: list[str] = []
|
||||
parameters: list[object] = []
|
||||
for column, value in (
|
||||
@@ -882,6 +890,10 @@ class QuestDefinitionsDao:
|
||||
"VALUES (?, ?, ?)",
|
||||
(definition_id, window, due_time),
|
||||
)
|
||||
if assignee_child_ids is not _UNSET:
|
||||
await self._replace_assignees(
|
||||
definition_id, assignee_child_ids
|
||||
)
|
||||
updated = await self.get(definition_id)
|
||||
assignees = await self.list_assignees(definition_id)
|
||||
window_records = await self.list_windows(definition_id)
|
||||
@@ -897,6 +909,36 @@ class QuestDefinitionsDao:
|
||||
windows=window_records,
|
||||
)
|
||||
|
||||
async def _replace_assignees(
|
||||
self, definition_id: int, child_ids: list[int]
|
||||
) -> None:
|
||||
"""Make the definition's assignee links exactly ``child_ids``.
|
||||
|
||||
Removes the links for children not in ``child_ids`` and inserts
|
||||
the missing ones. MUST be called inside the connection lock and
|
||||
an open transaction, after the ids were validated as assignable.
|
||||
"""
|
||||
rows = await self._database.fetch_all(
|
||||
"SELECT child_id FROM quest_definition_assignees "
|
||||
"WHERE definition_id = ?",
|
||||
(definition_id,),
|
||||
)
|
||||
current = {row[0] for row in rows}
|
||||
wanted = set(child_ids)
|
||||
for child_id in sorted(current - wanted):
|
||||
await self._database.execute(
|
||||
"DELETE FROM quest_definition_assignees "
|
||||
"WHERE definition_id = ? AND child_id = ?",
|
||||
(definition_id, child_id),
|
||||
)
|
||||
for child_id in child_ids:
|
||||
if child_id not in current:
|
||||
await self._database.execute(
|
||||
"INSERT INTO quest_definition_assignees "
|
||||
"(definition_id, child_id) VALUES (?, ?)",
|
||||
(definition_id, child_id),
|
||||
)
|
||||
|
||||
async def _validate_rule_exists(self, schedule_rule_id: int) -> None:
|
||||
"""Raise ValueError unless the rule exists.
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ Sits between the Feature 09 service gate and the typed
|
||||
:class:`~.dao_rules.QuestDefinitionsDao`: callers get validation and the
|
||||
all-or-nothing create/edit here, and never touch the DAO SQL directly.
|
||||
:func:`create_quest_definition` is the create path,
|
||||
:func:`edit_quest_definition` the edit path, :func:`assign_child` and
|
||||
:func:`unassign_child` the assignment paths, and
|
||||
:func:`edit_quest_definition` the edit path (which may also replace the
|
||||
whole assignee set), :func:`assign_child` and
|
||||
:func:`unassign_child` the single-child assignment paths, and
|
||||
:func:`set_quest_definition_active` the deactivate/reactivate path.
|
||||
There is no permission check here (that is Feature 09).
|
||||
|
||||
@@ -286,10 +287,11 @@ async def edit_quest_definition(
|
||||
rule: ScheduleRule | object = _UNSET,
|
||||
windows: list[str | tuple[str, str | None]] | object = _UNSET,
|
||||
skip_on_away: bool | None = None,
|
||||
assignee_child_ids: list[int] | object = _UNSET,
|
||||
today: datetime.date | None = None,
|
||||
horizon_days: int | None = None,
|
||||
) -> CreatedQuestDefinition:
|
||||
"""Edit a quest definition's metadata, rule and windows atomically.
|
||||
"""Edit a quest definition's metadata, rule, windows and assignees atomically.
|
||||
|
||||
Arguments default to the sentinel ``_UNSET`` meaning "leave this
|
||||
field alone". A provided ``title`` is validated like create's
|
||||
@@ -302,9 +304,14 @@ async def edit_quest_definition(
|
||||
a ``const.QUEST_WINDOWS`` name, optionally with a strict HH:MM due
|
||||
time), matching create's non-empty contract. A provided
|
||||
``skip_on_away`` (a real bool) replaces the stored flag; ``None``
|
||||
leaves it unchanged. Assignment is NOT
|
||||
settable here. Raises ValueError when the definition does not
|
||||
exist or any argument is rejected.
|
||||
leaves it unchanged. A provided ``assignee_child_ids`` REPLACES the
|
||||
whole assignee set under create's contract (a non-empty list of
|
||||
distinct plain ints, each an existing ACTIVE child — the DAO checks
|
||||
that inside the edit's transaction): newly listed children are
|
||||
assigned and unlisted ones unassigned. Raises ValueError when the
|
||||
definition does not exist or any argument is rejected; a rejection
|
||||
writes nothing, so the stored assignees (and every other field)
|
||||
stay unchanged.
|
||||
|
||||
Returns the updated definition together with its decoded rule,
|
||||
assignees and windows — all read back inside the DAO's transaction,
|
||||
@@ -348,6 +355,10 @@ async def edit_quest_definition(
|
||||
if windows is not _UNSET:
|
||||
window_specs = _normalize_windows(windows)
|
||||
|
||||
assignee_ids: list[int] | object = _UNSET
|
||||
if assignee_child_ids is not _UNSET:
|
||||
assignee_ids = _validate_assignee_ids(assignee_child_ids)
|
||||
|
||||
skip_on_away_value: bool | object = _UNSET
|
||||
if skip_on_away is not None:
|
||||
skip_on_away_value = _validate_skip_on_away(skip_on_away)
|
||||
@@ -362,14 +373,17 @@ async def edit_quest_definition(
|
||||
rule=rule_storage,
|
||||
windows=window_specs,
|
||||
skip_on_away=skip_on_away_value,
|
||||
assignee_child_ids=assignee_ids,
|
||||
)
|
||||
except ValueError as error:
|
||||
# The DAO's missing-definition error names only the id; re-raise
|
||||
# so the public contract names the field the caller passed (matching
|
||||
# assign/unassign/deactivate).
|
||||
# The DAO's missing-definition and assignee errors name only the
|
||||
# id; re-raise so the public contract names the field the caller
|
||||
# passed (matching create/assign/unassign/deactivate).
|
||||
message = str(error)
|
||||
if message.startswith("quest definition "):
|
||||
raise ValueError(f"definition_id: {message}") from error
|
||||
if message.startswith("child "):
|
||||
raise ValueError(f"assignee_child_ids: {message}") from error
|
||||
raise
|
||||
|
||||
decoded_rule = schedule_rule_from_storage(
|
||||
|
||||
Reference in New Issue
Block a user