tarakan/lib/tarakan_web/live/repository_code_live.ex
Maxfield Luke af6077b9c3
All checks were successful
CI and deploy / Test (push) Successful in 4m52s
CI and deploy / Deploy production (push) Successful in 23s
Initial commit on Forgejo
Fresh repository history for elektrine/tarakan hosted at
https://git.elektrine.com/elektrine/tarakan.
2026-07-29 04:43:40 -04:00

856 lines
28 KiB
Elixir

defmodule TarakanWeb.RepositoryCodeLive do
use TarakanWeb, :live_view
alias Tarakan.Repositories
alias Tarakan.Repositories.Repository
alias Tarakan.RepositoryCode
alias Tarakan.RepositoryCode.{File, Tree}
alias Tarakan.RepositoryPath
alias Tarakan.Scans
alias TarakanWeb.RepositoryPaths
@commit_sha_pattern ~r/^[0-9a-f]{40}$/
@line_range_pattern ~r/^([1-9][0-9]{0,8})(?:-([1-9][0-9]{0,8}))?$/
@max_rendered_lines 10_000
@max_selected_line 1_000_000
@max_selected_span 1_000
@impl true
def mount(params, _session, socket) do
{action, params} = normalize_route_params(socket.assigns.live_action, params)
socket = assign(socket, :live_action, action)
case source_from_params(action, params, socket) do
{:ok, source} ->
repository = source.repository
if connected?(socket) do
Repositories.subscribe()
Scans.subscribe(repository.id)
end
{:ok,
socket
|> assign(:page_title, "Code · #{repository.owner}/#{repository.name}")
|> assign(:repository, repository)
|> assign(:clone_urls, RepositoryPaths.clone_urls(repository))
|> assign(:finding, source.finding)
|> assign(:finding_ref, source.finding && source.finding.public_id)
|> assign(:source_commit_sha, source.commit_sha)
|> assign(:source_path, source.path)
|> assign(:source_line_range, source.line_range)
|> assign(:commit_sha, nil)
|> assign(:path, "")
|> assign(:path_segments, [])
|> assign(:line_range, nil)
|> assign(:line_range_invalid?, false)
|> assign(:view_state, :loading)
|> assign(:browser_kind, nil)
|> assign(:tree, nil)
|> assign(:file, nil)
|> assign(:entry_count, 0)
|> assign(:visible_finding_count, 0)
|> assign(:line_count, 0)
|> assign(:line_range_outside_file?, false)
|> assign(:suspicious_controls?, false)
|> assign(:error_kind, nil)
|> assign(:request_id, nil)
|> assign(:branch_options, [])
|> assign(:selected_branch, repository.default_branch)
|> maybe_load_branch_options(action)
|> stream_configure(:entries, dom_id: &entry_dom_id/1)
|> stream_configure(:lines, dom_id: &line_dom_id/1)
|> stream(:entries, [])
|> stream(:lines, [])}
{:error, :not_found} ->
raise Ecto.NoResultsError, queryable: Repository
end
end
@impl true
def handle_params(params, _uri, socket) do
{action, params} = normalize_route_params(socket.assigns.live_action, params)
socket =
case action do
:entry -> prepare_entry_request(socket)
:finding -> prepare_finding_request(socket)
:show -> prepare_code_request(socket, params)
end
{:noreply, maybe_start_request(assign(socket, :live_action, action))}
end
@impl true
def handle_event("retry", _params, socket) do
{:noreply,
socket
|> assign(:view_state, :loading)
|> assign(:error_kind, nil)
|> maybe_start_request()}
end
# Switch the code browser to another branch tip (still commit-pinned under the hood).
def handle_event("select_branch", %{"branch" => branch}, socket) do
if socket.assigns.live_action == :finding do
{:noreply, put_flash(socket, :error, "Finding context is pinned to its report commit.")}
else
repository = socket.assigns.repository
branch = String.trim(to_string(branch || ""))
case RepositoryCode.resolve_branch_commit(repository, branch) do
{:ok, commit_sha} ->
path =
if is_binary(socket.assigns.path) and socket.assigns.path != "" do
split_path(socket.assigns.path)
else
[]
end
{:noreply,
socket
|> assign(:selected_branch, branch)
|> push_navigate(to: code_path(repository, commit_sha, path))}
{:error, _reason} ->
{:noreply,
put_flash(socket, :error, "Could not resolve branch #{inspect(branch)} to a commit.")}
end
end
end
@impl true
def handle_async(:browse, {:ok, {request_id, result}}, socket) do
if request_id == socket.assigns.request_id do
case reauthorize_view(socket) do
{:ok, authorized_socket} ->
{:noreply, apply_browse_result(authorized_socket, result)}
{:error, :not_found} ->
{:noreply, evict_source(socket)}
end
else
{:noreply, socket}
end
end
def handle_async(:browse, {:exit, _reason}, socket) do
{:noreply, assign_error(socket, :unavailable)}
end
@impl true
def handle_info(
{:repository_record_updated, %Repository{id: repository_id}},
%{assigns: %{repository: %Repository{id: repository_id}}} = socket
) do
case reauthorize_view(socket) do
{:ok, authorized_socket} -> {:noreply, refresh_finding_badges(authorized_socket)}
{:error, :not_found} -> {:noreply, evict_source(socket)}
end
end
def handle_info({event, %Repository{}}, socket)
when event in [:repository_registered, :repository_record_updated] do
{:noreply, socket}
end
def handle_info(
{:scan_updated, %{id: scan_id}},
%{assigns: %{live_action: :finding, finding: %{scan_id: scan_id}}} = socket
) do
case reauthorize_view(socket) do
{:ok, authorized_socket} -> {:noreply, authorized_socket}
{:error, :not_found} -> {:noreply, evict_source(socket)}
end
end
def handle_info({event, _scan}, socket) when event in [:scan_submitted, :scan_updated],
do: {:noreply, refresh_finding_badges(socket)}
defp source_from_params(:finding, %{"finding_ref" => finding_ref}, socket) do
with {:ok, {scan, finding}} <- Scans.get_finding(socket.assigns.current_scope, finding_ref),
%Repository{} = repository <-
Repositories.get_visible_repository(
scan.repository.host,
scan.repository.owner,
scan.repository.name,
socket.assigns.current_scope
) do
{:ok,
%{
repository: repository,
finding: finding,
commit_sha: scan.commit_sha,
path: finding.file_path,
line_range: finding_line_range(finding)
}}
end
end
defp source_from_params(_action, %{"host" => slug, "owner" => owner, "name" => name}, socket) do
with {:ok, host} <- Tarakan.Hosts.host_for_slug(slug),
%Repository{} = repository <-
Repositories.get_visible_repository(host, owner, name, socket.assigns.current_scope) do
{:ok,
%{
repository: repository,
finding: nil,
commit_sha: nil,
path: nil,
line_range: nil
}}
else
_not_visible -> {:error, :not_found}
end
end
defp source_from_params(_action, _params, _socket), do: {:error, :not_found}
# Resolves the bare GitHub-style routes (/owner/name...), which carry no
# :host segment. A handle-like first segment is a Tarakan-hosted
# repository. A host-like first segment means the bare pattern's literal
# ("code") swallowed a remote path - a repository literally named "code" -
# so shift the params: /github.com/rails/code[/code[/sha[/*path]]].
# The security tab of such a repository has no reachable URL form; those
# requests fall through to :not_found.
defp normalize_route_params(action, %{"owner" => owner, "name" => name} = params)
when not is_map_key(params, "host") do
if Tarakan.Hosts.host_segment?(owner) do
remote = %{"host" => owner, "owner" => name, "name" => "code"}
case {action, params["commit_sha"], params["path"]} do
{:code_entry, _sha, _path} ->
{:entry, remote}
{:show, "code", nil} ->
{:entry, remote}
{:show, "code", [commit_sha | path]} ->
{:show, remote |> Map.put("commit_sha", commit_sha) |> Map.put("path", path)}
_other ->
{action, params}
end
else
{action_without_code_alias(action), Map.put(params, "host", Repository.hosted_host())}
end
end
defp normalize_route_params(action, params), do: {action_without_code_alias(action), params}
# /:owner/:name/code is a distinct live action only so bare-route
# reinterpretation can tell it apart from /:owner/:name; everywhere else
# it behaves exactly like :entry.
defp action_without_code_alias(:code_entry), do: :entry
defp action_without_code_alias(action), do: action
defp prepare_entry_request(socket) do
prepare_request(socket, :resolve_entry, nil, "", nil, false)
end
defp prepare_finding_request(socket) do
prepare_request(
socket,
:browse_finding,
socket.assigns.source_commit_sha,
socket.assigns.source_path,
socket.assigns.source_line_range,
false
)
end
defp prepare_code_request(socket, params) do
with {:ok, commit_sha} <- normalize_commit_sha(params["commit_sha"]),
{:ok, path} <- normalize_route_path(params["path"]),
{:ok, line_range, line_range_invalid?} <- parse_line_range(params["lines"]) do
socket
|> prepare_request(
:browse,
commit_sha,
path,
line_range,
line_range_invalid?
)
|> sync_selected_branch(commit_sha)
else
{:error, reason} -> assign_error(socket, reason)
end
end
# After a branch switch (or deep link to a tip SHA), keep the picker in sync.
defp sync_selected_branch(socket, commit_sha) do
case RepositoryCode.branch_for_commit(socket.assigns.repository, commit_sha) do
{:ok, branch} -> assign(socket, :selected_branch, branch)
_unknown -> socket
end
end
defp prepare_request(
socket,
request_kind,
commit_sha,
path,
line_range,
line_range_invalid?
) do
request_id = System.unique_integer([:positive, :monotonic])
socket
|> assign(:request_id, request_id)
|> assign(:request_kind, request_kind)
|> assign(:commit_sha, commit_sha)
|> assign(:path, path)
|> assign(:path_segments, split_path(path))
|> assign(:line_range, line_range)
|> assign(:line_range_invalid?, line_range_invalid?)
|> assign(:view_state, :loading)
|> assign(:browser_kind, nil)
|> assign(:tree, nil)
|> assign(:file, nil)
|> assign(:entry_count, 0)
|> assign(:visible_finding_count, 0)
|> assign(:line_count, 0)
|> assign(:line_range_outside_file?, false)
|> assign(:suspicious_controls?, false)
|> assign(:error_kind, nil)
|> stream(:entries, [], reset: true)
|> stream(:lines, [], reset: true)
end
defp maybe_start_request(%{assigns: %{view_state: :loading}} = socket) do
if connected?(socket) do
request_id = socket.assigns.request_id
request_kind = socket.assigns.request_kind
repository = socket.assigns.repository
commit_sha = socket.assigns.commit_sha
path = socket.assigns.path
# Per-IP browsing budgets are enforced at the HTTP layer by
# TarakanWeb.Plugs.CodeBrowserRateLimit; the reads below stay on the
# default (non-bypassed) code path.
socket
|> cancel_async(:browse)
|> start_async(:browse, fn ->
{request_id, run_request(request_kind, repository, commit_sha, path)}
end)
else
socket
end
end
defp maybe_start_request(socket), do: socket
defp run_request(:browse, repository, commit_sha, path) do
# Generic browser may open any *current* branch tip (not only default),
# but still rejects guessed historical SHAs that are not branch tips.
with :ok <- RepositoryCode.authorize_public_commit(repository, commit_sha),
{:ok, result} <- RepositoryCode.browse(repository, commit_sha, path, []) do
{:ok, result}
else
# Never show the old "busy network" page for GitHub REST quota.
{:error, :rate_limited} -> {:error, :unavailable}
{:error, reason} -> {:error, reason}
end
end
defp run_request(:browse_finding, repository, commit_sha, path) do
case RepositoryCode.browse(repository, commit_sha, path, []) do
{:error, :rate_limited} -> {:error, :unavailable}
other -> other
end
end
defp run_request(:resolve_entry, repository, _commit_sha, _path) do
with {:ok, commit_sha} <- RepositoryCode.resolve_default_commit(repository, []),
{:ok, result} <- RepositoryCode.browse(repository, commit_sha, "", []) do
{:ok, result}
else
{:error, :rate_limited} -> {:error, :unavailable}
{:error, reason} -> {:error, reason}
end
end
defp apply_browse_result(socket, {:ok, %Tree{truncated: true}}) do
assign_error(socket, :tree_truncated)
end
defp apply_browse_result(socket, {:ok, %Tree{} = tree}) do
finding_paths = visible_finding_paths(socket, tree.commit_sha)
entries =
tree.entries
|> sort_entries()
|> Enum.map(&entry_with_finding_count(&1, finding_paths))
socket
|> assign(:view_state, :ready)
|> assign(:browser_kind, :tree)
|> assign(:commit_sha, tree.commit_sha)
|> assign(:path, tree.path)
|> assign(:path_segments, split_path(tree.path))
|> assign(:tree, tree)
|> assign(:file, nil)
|> assign(:entry_count, length(entries))
|> assign(:visible_finding_count, length(finding_paths))
|> assign(:line_count, 0)
|> assign(:line_range_outside_file?, false)
|> assign(:suspicious_controls?, false)
|> assign(:error_kind, nil)
|> stream(:entries, entries, reset: true)
|> stream(:lines, [], reset: true)
end
defp apply_browse_result(socket, {:ok, %File{} = file}) do
case file_lines(file.content, socket.assigns.line_range) do
{:ok, lines} ->
line_count = length(lines)
finding_count = visible_file_finding_count(socket, file.commit_sha, file.path)
socket
|> assign(:view_state, :ready)
|> assign(:browser_kind, :file)
|> assign(:commit_sha, file.commit_sha)
|> assign(:path, file.path)
|> assign(:path_segments, split_path(file.path))
|> assign(:tree, nil)
|> assign(:file, file)
|> assign(:entry_count, 0)
|> assign(:visible_finding_count, finding_count)
|> assign(:line_count, line_count)
|> assign(
:line_range_outside_file?,
line_range_outside_file?(socket.assigns.line_range, line_count)
)
|> assign(:suspicious_controls?, suspicious_source_controls?(file.content))
|> assign(:error_kind, nil)
|> stream(:entries, [], reset: true)
|> stream(:lines, lines, reset: true)
{:error, :too_many_lines} ->
assign_error(socket, :blob_too_large)
end
end
defp apply_browse_result(socket, {:ok, commit_sha}) when is_binary(commit_sha) do
case normalize_commit_sha(commit_sha) do
{:ok, commit_sha} ->
push_navigate(socket,
to: code_path(socket.assigns.repository, commit_sha, [])
)
{:error, _reason} ->
assign_error(socket, :invalid_response)
end
end
defp apply_browse_result(socket, {:error, reason}), do: assign_error(socket, reason)
defp apply_browse_result(socket, _unexpected), do: assign_error(socket, :invalid_response)
defp assign_error(socket, reason) do
socket
|> assign(:view_state, :error)
|> assign(:browser_kind, nil)
|> assign(:tree, nil)
|> assign(:file, nil)
|> assign(:entry_count, 0)
|> assign(:visible_finding_count, 0)
|> assign(:line_count, 0)
|> assign(:line_range_outside_file?, false)
|> assign(:suspicious_controls?, false)
|> assign(:error_kind, normalize_error(reason))
|> stream(:entries, [], reset: true)
|> stream(:lines, [], reset: true)
end
defp normalize_error(reason)
when reason in [
:invalid_commit_sha,
:invalid_path,
:empty_repository,
:identity_changed,
:commit_mismatch,
:not_found,
:tree_truncated,
:tree_too_large,
:blob_too_large,
:binary_blob,
:unsupported_entry,
:rate_limited,
:unavailable,
:invalid_response
],
do: reason
defp normalize_error(_reason), do: :unavailable
defp normalize_commit_sha(commit_sha) when is_binary(commit_sha) do
normalized = String.downcase(commit_sha)
if Regex.match?(@commit_sha_pattern, normalized) do
{:ok, normalized}
else
{:error, :invalid_commit_sha}
end
end
defp normalize_commit_sha(_commit_sha), do: {:error, :invalid_commit_sha}
defp normalize_route_path(nil), do: {:ok, ""}
defp normalize_route_path(path) when is_list(path) do
path
|> Enum.join("/")
|> RepositoryPath.normalize()
end
defp normalize_route_path(_path), do: {:error, :invalid_path}
defp parse_line_range(nil), do: {:ok, nil, false}
defp parse_line_range(""), do: {:ok, nil, false}
defp parse_line_range(value) when is_binary(value) do
case Regex.run(@line_range_pattern, value, capture: :all_but_first) do
[first, ""] -> validate_single_line(first)
[first] -> validate_single_line(first)
[first, last] -> validate_line_range(String.to_integer(first), String.to_integer(last))
nil -> {:ok, nil, true}
end
end
defp parse_line_range(_value), do: {:ok, nil, true}
defp validate_single_line(line) do
line = String.to_integer(line)
validate_line_range(line, line)
end
defp validate_line_range(first, last)
when last >= first and last <= @max_selected_line and
last - first <= @max_selected_span,
do: {:ok, {first, last}, false}
defp validate_line_range(_first, _last), do: {:ok, nil, true}
defp finding_line_range(%{line_start: line_start, line_end: line_end})
when is_integer(line_start) do
{line_start, line_end || line_start}
end
defp finding_line_range(_finding), do: nil
defp split_path(""), do: []
defp split_path(path), do: String.split(path, "/", trim: true)
defp file_lines("", _line_range), do: {:ok, []}
defp file_lines(content, line_range) do
lines = String.split(content, "\n", parts: @max_rendered_lines + 1, trim: false)
if length(lines) > @max_rendered_lines do
{:error, :too_many_lines}
else
{:ok,
lines
|> Enum.with_index(1)
|> Enum.map(fn {content, number} ->
%{number: number, content: content, highlighted?: line_selected?(number, line_range)}
end)}
end
end
defp line_selected?(_number, nil), do: false
defp line_selected?(number, {first, last}), do: number >= first and number <= last
defp line_range_outside_file?(nil, _line_count), do: false
defp line_range_outside_file?({first, last}, line_count),
do: first > line_count or last > line_count
defp sort_entries(entries) do
Enum.sort_by(entries, fn entry ->
{entry_type_order(entry.type), String.downcase(entry.name), entry.name}
end)
end
defp entry_with_finding_count(entry, finding_paths) do
count =
case entry.type do
:tree ->
prefix = entry.path <> "/"
Enum.count(finding_paths, &String.starts_with?(&1, prefix))
:blob ->
Enum.count(finding_paths, &(&1 == entry.path))
_unsupported ->
0
end
entry
|> Map.from_struct()
|> Map.put(:finding_count, count)
end
defp visible_file_finding_count(socket, commit_sha, path) do
socket
|> visible_finding_paths(commit_sha)
|> Enum.count(&(&1 == path))
end
defp visible_finding_paths(socket, commit_sha) do
socket.assigns.current_scope
|> Scans.list_scans(socket.assigns.repository)
|> Enum.filter(
&(&1.commit_sha == commit_sha and &1.details_visible and &1.review_status != "rejected")
)
|> Enum.flat_map(& &1.findings)
|> Enum.map(& &1.file_path)
end
defp refresh_finding_badges(%{assigns: %{browser_kind: :tree, tree: %Tree{} = tree}} = socket),
do: apply_browse_result(socket, {:ok, tree})
defp refresh_finding_badges(%{assigns: %{browser_kind: :file, file: %File{} = file}} = socket) do
assign(
socket,
:visible_finding_count,
visible_file_finding_count(socket, file.commit_sha, file.path)
)
end
defp refresh_finding_badges(socket), do: socket
defp entry_type_order(:tree), do: 0
defp entry_type_order(:blob), do: 1
defp entry_type_order(:symlink), do: 2
defp entry_type_order(:submodule), do: 3
defp entry_dom_id(entry) do
encoded_path = Base.url_encode64(entry.path, padding: false)
"code-entry-#{encoded_path}"
end
defp line_dom_id(line), do: "L#{line.number}"
defp repository_record_path(repository), do: RepositoryPaths.repository_path(repository)
defp repository_security_path(repository),
do: RepositoryPaths.repository_security_path(repository)
defp code_path(repository, commit_sha, path_segments),
do: RepositoryPaths.repository_code_path(repository, commit_sha, path_segments)
# Finding views stay on their report pin; free browse can switch branches.
defp maybe_load_branch_options(socket, :finding), do: socket
defp maybe_load_branch_options(socket, _action) do
repository = socket.assigns.repository
case RepositoryCode.list_branches(repository) do
{:ok, branches} ->
socket
|> assign(:branch_options, branches)
|> assign(
:selected_branch,
socket.assigns.selected_branch || repository.default_branch || List.first(branches)
)
{:error, _} ->
default = repository.default_branch
branches = if is_binary(default) and default != "", do: [default], else: []
socket
|> assign(:branch_options, branches)
|> assign(:selected_branch, default)
end
end
defp breadcrumb_items(path_segments) do
path_segments
|> Enum.with_index()
|> Enum.map(fn {name, index} ->
%{name: name, segments: Enum.take(path_segments, index + 1)}
end)
end
defp entry_path(repository, commit_sha, entry) do
code_path(repository, commit_sha, split_path(entry.path))
end
defp breadcrumb_path(repository, commit_sha, segments) do
code_path(repository, commit_sha, segments)
end
defp line_path(live_action, finding_ref, repository, commit_sha, path_segments, line_number) do
base =
if live_action == :finding do
~p"/findings/#{finding_ref}/code"
else
code_path(repository, commit_sha, path_segments)
end
if live_action == :finding do
base <> "#L#{line_number}"
else
base <> "?lines=#{line_number}#L#{line_number}"
end
end
defp format_bytes(nil), do: "-"
defp format_bytes(bytes) when bytes < 1_024, do: "#{bytes} B"
defp format_bytes(bytes) when bytes < 1_024 * 1_024,
do: "#{Float.round(bytes / 1_024, 1)} KB"
defp format_bytes(bytes), do: "#{Float.round(bytes / (1_024 * 1_024), 1)} MB"
defp short_sha(sha), do: String.slice(sha, 0, 7)
defp entry_icon(:tree), do: "hero-folder"
defp entry_icon(:blob), do: "hero-document-text"
defp entry_icon(:symlink), do: "hero-link"
defp entry_icon(:submodule), do: "hero-cube"
defp entry_label(:symlink), do: "symbolic link"
defp entry_label(:submodule), do: "submodule"
defp error_title(:invalid_commit_sha), do: "Invalid commit"
defp error_title(:invalid_path), do: "Invalid path"
defp error_title(:not_found), do: "Source not found"
defp error_title(:binary_blob), do: "Binary file"
defp error_title(:blob_too_large), do: "File too large"
defp error_title(:tree_too_large), do: "Directory too large"
defp error_title(:tree_truncated), do: "Incomplete directory"
defp error_title(:unsupported_entry), do: "Unsupported source entry"
defp error_title(:empty_repository), do: "Empty repository"
defp error_title(:rate_limited), do: "Code unavailable"
defp error_title(:identity_changed), do: "Repository identity changed"
defp error_title(:commit_mismatch), do: "Commit verification failed"
defp error_title(_error), do: "Code unavailable"
defp error_message(:invalid_commit_sha),
do: "Code views require a full 40-character commit SHA."
defp error_message(:invalid_path), do: "That repository path is not valid."
defp error_message(:not_found),
do: "No source exists at this path in the pinned commit."
defp error_message(:binary_blob),
do: "Tarakan does not render binary files as source code."
defp error_message(:blob_too_large),
do: "This file exceeds the safe source-preview limit."
defp error_message(reason) when reason in [:tree_too_large, :tree_truncated],
do: "This directory cannot be represented safely as a complete listing."
defp error_message(:unsupported_entry),
do: "This Git object type cannot be opened in the source browser."
defp error_message(:empty_repository),
do: "Nothing has been pushed to this repository yet. Push a branch and it will appear here."
defp error_message(:rate_limited),
do:
"Could not load this commit from git or GitHub yet. Wait a few seconds for the mirror to fill, then retry."
defp error_message(:unavailable),
do:
"Could not load this commit from git or GitHub yet. Wait a few seconds for the mirror to fill, then retry."
defp error_message(reason) when reason in [:identity_changed, :commit_mismatch],
do: "Tarakan could not verify this source against the registered public repository."
defp error_message(_error),
do:
"Could not load this commit from git or GitHub yet. Wait a few seconds for the mirror to fill, then retry."
defp suspicious_source_controls?(content) do
String.contains?(content, <<0>>) or
Enum.any?([0x202A, 0x202B, 0x202C, 0x202D, 0x202E, 0x2066, 0x2067, 0x2068, 0x2069], fn
codepoint -> String.contains?(content, <<codepoint::utf8>>)
end)
end
defp reauthorize_view(%{assigns: %{live_action: :finding}} = socket) do
repository = socket.assigns.repository
with %Repository{} = visible_repository <-
Repositories.get_visible_repository(
repository.host,
repository.owner,
repository.name,
socket.assigns.current_scope
),
{:ok, {scan, finding}} <-
Scans.get_finding(socket.assigns.current_scope, socket.assigns.finding_ref),
true <- scan.repository_id == visible_repository.id,
true <- scan.commit_sha == socket.assigns.source_commit_sha,
true <- finding.file_path == socket.assigns.source_path do
{:ok,
socket
|> assign(:repository, visible_repository)
|> assign(:finding, finding)}
else
_not_visible -> {:error, :not_found}
end
end
defp reauthorize_view(socket) do
repository = socket.assigns.repository
case Repositories.get_visible_repository(
repository.host,
repository.owner,
repository.name,
socket.assigns.current_scope
) do
%Repository{} = visible_repository ->
{:ok, assign(socket, :repository, visible_repository)}
nil ->
{:error, :not_found}
end
end
defp evict_source(socket) do
repository = socket.assigns.repository
destination =
case Repositories.get_visible_repository(
repository.host,
repository.owner,
repository.name,
socket.assigns.current_scope
) do
%Repository{} = visible_repository -> repository_record_path(visible_repository)
nil -> ~p"/"
end
socket
|> clear_source()
|> push_navigate(to: destination)
end
defp clear_source(socket) do
socket
|> assign(:view_state, :loading)
|> assign(:browser_kind, nil)
|> assign(:commit_sha, nil)
|> assign(:path, "")
|> assign(:path_segments, [])
|> assign(:line_range, nil)
|> assign(:finding, nil)
|> assign(:tree, nil)
|> assign(:file, nil)
|> assign(:entry_count, 0)
|> assign(:visible_finding_count, 0)
|> assign(:line_count, 0)
|> assign(:line_range_outside_file?, false)
|> assign(:suspicious_controls?, false)
|> assign(:error_kind, nil)
|> stream(:entries, [], reset: true)
|> stream(:lines, [], reset: true)
end
end