user(); if (! $user instanceof User) { throw new AuthenticationException('Authentication required.'); } return $user; } /** * @throws AuthorizationException */ public function ensureCanRead(User $user, Project $project): void { if (! $this->permissionService->allows($user, Permission::ProjectRead, $project)) { throw new AuthorizationException('You cannot view this project.'); } } /** * @throws AuthorizationException */ public function ensureCanWrite(User $user, Project $project): void { if (! $this->permissionService->allows($user, Permission::ProjectWrite, $project)) { throw new AuthorizationException('You cannot update tickets in this project.'); } } /** * @throws AuthorizationException */ public function ticketForRead(User $user, int $ticketId): Ticket { $ticket = Ticket::query() ->with('project') ->find($ticketId); if ($ticket === null) { throw ValidationException::withMessages([ 'ticket_id' => 'Ticket was not found.', ]); } $this->ensureCanRead($user, $ticket->project); return $ticket; } /** * @throws AuthorizationException */ public function ticketForWrite(User $user, int $ticketId): Ticket { $ticket = Ticket::query() ->with('project') ->find($ticketId); if ($ticket === null) { throw ValidationException::withMessages([ 'ticket_id' => 'Ticket was not found.', ]); } $this->ensureCanWrite($user, $ticket->project); return $ticket; } /** * @throws AuthorizationException */ public function projectForRead(User $user, ?int $projectId): ?Project { if ($projectId === null) { return null; } $project = Project::query()->find($projectId); if ($project === null) { throw ValidationException::withMessages([ 'project_id' => 'Project was not found.', ]); } $this->ensureCanRead($user, $project); return $project; } /** * @param array $validated * @return Collection|Response */ public function resolveTicketStatuses(User $user, array $validated, ?Project $project = null): Collection|Response { $projectIds = $project === null ? $this->permissionService->viewableProjectIds($user) : [$project->id]; if ($projectIds === []) { return collect(); } $ticketStatusId = $validated['ticket_status_id'] ?? null; if ($ticketStatusId !== null) { $ticketStatus = TicketStatus::query() ->whereIn('project_id', $projectIds) ->with('boardColumn') ->find((int) $ticketStatusId); return $ticketStatus === null ? Response::error('Ticket status was not found in a visible project.') : collect([$ticketStatus]); } $query = TicketStatus::query() ->whereIn('project_id', $projectIds) ->with(['boardColumn', 'project']) ->orderBy('project_id') ->orderBy('position'); if (($statusSlug = $validated['status_slug'] ?? null) !== null) { $query->where('slug', $statusSlug); } elseif (($statusName = $validated['status_name'] ?? null) !== null) { $query->where('name', $statusName); } else { return Response::error('Provide ticket_status_id, status_slug, or status_name.'); } $ticketStatuses = $query->get(); if ($ticketStatuses->count() <= 1) { return $ticketStatuses; } $matches = $ticketStatuses ->map(fn (TicketStatus $ticketStatus): array => [ 'id' => $ticketStatus->id, 'name' => $ticketStatus->name, 'project_id' => $ticketStatus->project_id, 'project_name' => $ticketStatus->project?->name, 'slug' => $ticketStatus->slug, ]) ->values() ->all(); return Response::error(sprintf( 'Status lookup is ambiguous. Provide ticket_status_id or project_id. Matches: %s', json_encode($matches, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES), )); } /** * @return array */ public function ticketPayload(Ticket $ticket): array { $ticket->load([ 'assignee', 'author', 'fieldValues.ticketFieldDefinition', 'fieldValues.userValue', 'project', 'ticketPriority', 'ticketStatus.boardColumn', 'ticketType', 'worklogEntries.actor', ]); return [ 'project' => ProjectResource::make($ticket->project)->resolve(), 'ticket' => TicketResource::make($ticket)->resolve(), ]; } /** * @return array{ * acceptance_criteria: array, * assignee_user_id: int|null, * author_user_id: int|null, * blocked_comment: string|null, * description: string|null, * is_blocked: bool, * parent_ticket_id: int|null, * story_points: int|null, * ticket_priority_id: int, * ticket_type_id: int, * title: string * } */ public function updatePayload(Ticket $ticket): array { return [ 'acceptance_criteria' => AcceptanceCriteriaChecklistMapper::fromModels( $ticket->relationLoaded('acceptanceCriteria') ? $ticket->acceptanceCriteria : $ticket->acceptanceCriteria()->get(), ), 'assignee_user_id' => $ticket->assignee_user_id, 'author_user_id' => $ticket->author_user_id, 'blocked_comment' => $ticket->blocked_comment, 'description' => $ticket->description, 'is_blocked' => $ticket->is_blocked, 'parent_ticket_id' => $ticket->parent_ticket_id, 'story_points' => $ticket->story_points, 'ticket_priority_id' => $ticket->ticket_priority_id, 'ticket_type_id' => $ticket->ticket_type_id, 'title' => $ticket->title, ]; } }