*/ class ThreadTaskMapper extends QBMapper { public function __construct(IDBConnection $db) { parent::__construct($db, 'nuqloud_scrum_threads', ThreadTask::class); } public function findByRootMessage(int $workspaceId, int $talkRootMessageId): ThreadTask { $qb = $this->db->getQueryBuilder(); $qb->select('*') ->from($this->getTableName()) ->where($qb->expr()->eq('workspace_id', $qb->createNamedParameter($workspaceId, IQueryBuilder::PARAM_INT))) ->andWhere($qb->expr()->eq('talk_root_message_id', $qb->createNamedParameter($talkRootMessageId, IQueryBuilder::PARAM_INT))); return $this->findEntity($qb); } public function findByTalkThreadId(int $workspaceId, string $talkThreadId): ThreadTask { $qb = $this->db->getQueryBuilder(); $qb->select('*') ->from($this->getTableName()) ->where($qb->expr()->eq('workspace_id', $qb->createNamedParameter($workspaceId, IQueryBuilder::PARAM_INT))) ->andWhere($qb->expr()->eq('talk_thread_id', $qb->createNamedParameter($talkThreadId, IQueryBuilder::PARAM_STR))); return $this->findEntity($qb); } /** @return ThreadTask[] */ public function findByWorkspace(int $workspaceId): array { $qb = $this->db->getQueryBuilder(); $qb->select('*') ->from($this->getTableName()) ->where($qb->expr()->eq('workspace_id', $qb->createNamedParameter($workspaceId, IQueryBuilder::PARAM_INT))) ->orderBy('updated_at', 'DESC'); return $this->findEntities($qb); } /** @param array $fields */ public function upsertTask(int $workspaceId, int $talkRootMessageId, array $fields): ThreadTask { $now = time(); try { $task = $this->findByRootMessage($workspaceId, $talkRootMessageId); } catch (DoesNotExistException) { $task = new ThreadTask(); $task->setWorkspaceId($workspaceId); $task->setTalkRootMessageId($talkRootMessageId); $task->setStatus('inbox'); $task->setPriority('normal'); $task->setCreatedAt($now); } $task->setTalkThreadId($this->nullableString($fields['talkThreadId'] ?? $task->getTalkThreadId())); $task->setTitle($this->boundedString($fields['title'] ?? $task->getTitle(), 255, 'Untitled task')); $task->setSlug($this->boundedString($fields['slug'] ?? $task->getSlug(), 255, (string)$talkRootMessageId)); $task->setFolderPath($this->boundedString($fields['folderPath'] ?? $task->getFolderPath(), 1024, '')); $task->setCreatedBy($this->nullableString($fields['createdBy'] ?? $task->getCreatedBy())); if (array_key_exists('lastActivityMessageId', $fields)) { $task->setLastActivityMessageId(max($task->getLastActivityMessageId(), (int)$fields['lastActivityMessageId'])); } $task->setUpdatedAt($now); return $task->getId() === null ? $this->insert($task) : $this->update($task); } public function setStatus(ThreadTask $task, string $status): ThreadTask { $task->setStatus($this->boundedString($status, 32, 'inbox')); $task->setUpdatedAt(time()); return $this->update($task); } private function nullableString(mixed $value): ?string { $value = trim((string)$value); return $value === '' ? null : $value; } private function boundedString(mixed $value, int $maxLength, string $fallback): string { $value = trim((string)$value); if ($value === '') { $value = $fallback; } if (function_exists('mb_substr')) { return mb_substr($value, 0, $maxLength); } return substr($value, 0, $maxLength); } }