|
1 | 1 | defmodule CodeCorps.GitHub.Sync.Comment do |
2 | | - alias CodeCorps.GitHub.Sync |
3 | | - alias Ecto.Multi |
| 2 | + @moduledoc ~S""" |
| 3 | + In charge of syncing `CodeCorps.Comment` records with a GitHub comment |
| 4 | + payload. |
| 5 | + |
| 6 | + A single GitHub comment always matches a single `CodeCorps.GithubComment`, but |
| 7 | + it can match multiple `CodeCorps.Comment` records. This module handles |
| 8 | + creating or updating all those records. |
| 9 | + """ |
| 10 | + |
| 11 | + import Ecto.Query |
| 12 | + |
| 13 | + alias CodeCorps.{ |
| 14 | + Comment, |
| 15 | + GitHub.Sync, |
| 16 | + GitHub.Utils.ResultAggregator, |
| 17 | + GithubComment, |
| 18 | + GithubIssue, |
| 19 | + GithubRepo, |
| 20 | + GithubUser, |
| 21 | + Repo, |
| 22 | + Task, |
| 23 | + User |
| 24 | + } |
| 25 | + alias Ecto.Changeset |
| 26 | + |
| 27 | + @type commit_result_aggregate :: |
| 28 | + {:ok, list(Comment.t())} | {:error, {list(Comment.t()), list(Changeset.t())}} |
| 29 | + |
| 30 | + @type commit_result :: {:ok, Comment.t()} | {:error, Changeset.t()} |
4 | 31 |
|
5 | 32 | @doc ~S""" |
6 | | - Creates an `Ecto.Multi` intended to process a GitHub issue comment related API |
7 | | - payload. |
| 33 | + Creates or updates a `CodeCorps.Comment` for the specified `CodeCorps.Task`. |
| 34 | + |
| 35 | + When provided a `CodeCorps.Task`, a `CodeCorps.GithubComment`, a |
| 36 | + `CodeCorps.User`, and a GitHub API payload, it creates or updates a |
| 37 | + `CodeCorps.Comment` record, using the provided GitHub API |
| 38 | + payload, associated to the specified `CodeCorps.GithubComment`, |
| 39 | + `CodeCorps.Task` and `CodeCorps.User` |
| 40 | + """ |
| 41 | + @spec sync(Task.t(), GithubComment.t(), User.t()) :: commit_result() |
| 42 | + def sync(%Task{} = task, %GithubComment{} = github_comment, %User{} = user) do |
| 43 | + case find_comment(task, github_comment) do |
| 44 | + nil -> |
| 45 | + github_comment |
| 46 | + |> Sync.Comment.Changeset.create_changeset(task, user) |
| 47 | + |> Repo.insert() |
| 48 | + |
| 49 | + %Comment{} = comment -> |
| 50 | + comment |
| 51 | + |> Sync.Comment.Changeset.update_changeset(github_comment) |
| 52 | + |> Repo.update() |
| 53 | + end |
| 54 | + end |
| 55 | + |
| 56 | + @spec find_comment(Task.t(), GithubComment.t()) :: Comment.t() | nil |
| 57 | + defp find_comment(%Task{id: task_id}, %GithubComment{id: github_comment_id}) do |
| 58 | + query = from c in Comment, |
| 59 | + where: c.task_id == ^task_id, |
| 60 | + join: gc in GithubComment, on: c.github_comment_id == gc.id, where: gc.id == ^github_comment_id |
| 61 | + |
| 62 | + query |> Repo.one() |
| 63 | + end |
8 | 64 |
|
9 | | - Expects a partial transaction outcome with `:github_issue` and :task keys. |
| 65 | + @doc ~S""" |
| 66 | + Creates or updates `CodeCorps.Comment` records for the specified |
| 67 | + `CodeCorps.GithubRepo`. |
10 | 68 |
|
11 | | - Returns an `Ecto.Multi` with the follwing steps |
| 69 | + For each `CodeCorps.GithubComment` record that relates to the |
| 70 | + `CodeCorps.GithubRepo` for a given `CodeCorps.GithubRepo`: |
12 | 71 |
|
13 | | - - create or update a `CodeCorps.GithubComment` from the |
14 | | - provided `CodeCorps.GithubIssue` and API payload |
15 | | - - match the `CodeCorps.GithubComment` with a new or existing `CodeCorps.User` |
16 | | - - create or update a `CodeCorps.Comment` using the created |
17 | | - `CodeCorps.GithubComment`, related to the matched `CodeCorps.User` and the |
18 | | - provided `CodeCorps.Task` |
| 72 | + - Find the related `CodeCorps.Task` record |
| 73 | + - Create or update the related `CodeCorps.Comment` record |
| 74 | + - Associate the `CodeCorps.Comment` record with the `CodeCorps.User` that |
| 75 | + relates to the `CodeCorps.GithubUser` for the `CodeCorps.GithubComment` |
19 | 76 | """ |
20 | | - @spec sync(map, map) :: Multi.t |
21 | | - def sync(%{github_issue: github_issue, task: task}, %{} = payload) do |
22 | | - Multi.new |
23 | | - |> Multi.run(:github_comment, fn _ -> Sync.Comment.GithubComment.create_or_update_comment(github_issue, payload) end) |
24 | | - |> Multi.run(:comment_user, fn %{github_comment: github_comment} -> Sync.User.RecordLinker.link_to(github_comment, payload) end) |
25 | | - |> Multi.run(:comment, fn %{github_comment: github_comment, comment_user: user} -> Sync.Comment.Comment.sync(task, github_comment, user) end) |
| 77 | + @spec sync_github_repo(GithubRepo.t()) :: commit_result_aggregate() |
| 78 | + def sync_github_repo(%GithubRepo{} = github_repo) do |
| 79 | + preloads = [ |
| 80 | + github_comments: [:github_issue, github_user: [:user]] |
| 81 | + ] |
| 82 | + %GithubRepo{github_comments: github_comments} = |
| 83 | + github_repo |> Repo.preload(preloads) |
| 84 | + |
| 85 | + github_comments |
| 86 | + |> Enum.map(fn %GithubComment{github_user: %GithubUser{user: %User{} = user}} = github_comment -> |
| 87 | + github_comment |
| 88 | + |> find_task(github_repo) |
| 89 | + |> sync(github_comment, user) |
| 90 | + end) |
| 91 | + |> ResultAggregator.aggregate() |
| 92 | + end |
| 93 | + |
| 94 | + # TODO: can this return a nil? |
| 95 | + @spec find_task(GithubComment.t(), GithubRepo.t()) :: Task.t() |
| 96 | + defp find_task( |
| 97 | + %GithubComment{github_issue: %GithubIssue{id: github_issue_id}}, |
| 98 | + %GithubRepo{project_id: project_id}) do |
| 99 | + query = from t in Task, |
| 100 | + where: t.project_id == ^project_id, |
| 101 | + join: gi in GithubIssue, on: t.github_issue_id == gi.id, where: gi.id == ^github_issue_id |
| 102 | + |
| 103 | + query |> Repo.one() |
26 | 104 | end |
27 | 105 |
|
28 | 106 | @doc ~S""" |
29 | | - Creates an `Ecto.Multi` intended to delete a `CodeCorps.GithubComment` |
30 | | - specified by `github_id`, as well as 0 to 1 `CodeCorps.Comment` records |
31 | | - associated to `CodeCorps.GithubComment` |
| 107 | + Deletes `CodeCorps.Comment` records associated to `CodeCorps.GithubComment` |
| 108 | + with specified `github_id` |
| 109 | + |
| 110 | + Since there can be 0 or 1 such records, returns `{:ok, results}` where |
| 111 | + `results` is a 1-element or blank list of deleted records. |
32 | 112 | """ |
33 | | - @spec delete(map) :: Multi.t |
34 | | - def delete(%{"id" => github_id}) do |
35 | | - Multi.new |
36 | | - |> Multi.run(:deleted_comments, fn _ -> Sync.Comment.Comment.delete(github_id) end) |
37 | | - |> Multi.run(:deleted_github_comment, fn _ -> Sync.Comment.GithubComment.delete(github_id) end) |
| 113 | + @spec delete(String.t()) :: {:ok, list(Comment.t())} |
| 114 | + def delete(github_id) do |
| 115 | + query = |
| 116 | + from c in Comment, |
| 117 | + join: gc in GithubComment, on: gc.id == c.github_comment_id, where: gc.github_id == ^github_id |
| 118 | + |
| 119 | + query |
| 120 | + |> Repo.delete_all(returning: true) |
| 121 | + |> (fn {_count, comments} -> {:ok, comments} end).() |
38 | 122 | end |
39 | 123 | end |
0 commit comments