Skip to content

Commit d454f26

Browse files
authored
feat: add cached og images for repos (#19)
1 parent 5e03441 commit d454f26

File tree

5 files changed

+127
-11
lines changed

5 files changed

+127
-11
lines changed

config/config.exs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ config :algora, AlgoraWeb.Endpoint,
2828

2929
config :algora, Oban,
3030
repo: Algora.Repo,
31-
queues: [event_consumers: 1, comment_consumers: 1]
31+
queues: [
32+
event_consumers: 1,
33+
comment_consumers: 1,
34+
github_og_image: 5
35+
]
3236

3337
# Configures the mailer
3438
#
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
defmodule Algora.Workspace.Jobs.UpdateRepositoryOgImage do
2+
@moduledoc false
3+
use Oban.Worker, queue: :github_og_image
4+
5+
alias Algora.Repo
6+
alias Algora.Workspace.Repository
7+
8+
require Logger
9+
10+
@impl Oban.Worker
11+
def perform(%Oban.Job{args: %{"repository_id" => repository_id}}) do
12+
repository =
13+
Repository
14+
|> Repo.get(repository_id)
15+
|> Repo.preload(:user)
16+
17+
case repository do
18+
nil -> {:error, :not_found}
19+
repository -> update_og_image(repository)
20+
end
21+
end
22+
23+
defp update_og_image(repository) do
24+
repo_owner = repository.user.provider_login
25+
repo_name = repository.name
26+
object = "repositories/#{repo_owner}/#{repo_name}/og.png"
27+
28+
req = Finch.build(:get, repository.og_image_url)
29+
30+
with {:ok, %Finch.Response{body: body}} <- Finch.request(req, Algora.Finch),
31+
{:ok, _} <- Algora.S3.upload(body, object, content_type: "image/png"),
32+
url = Algora.S3.bucket_url() <> "/" <> object,
33+
{:ok, updated_repository} <- update_repository_url(repository, url) do
34+
{:ok, updated_repository}
35+
else
36+
error ->
37+
Logger.error("Failed to fetch/upload image for #{repo_owner}/#{repo_name}: #{inspect(error)}")
38+
error
39+
end
40+
end
41+
42+
defp update_repository_url(repository, url) do
43+
repository
44+
|> Ecto.Changeset.change(%{
45+
og_image_url: url,
46+
og_image_updated_at: DateTime.utc_now()
47+
})
48+
|> Repo.update()
49+
end
50+
end

lib/algora/workspace/schemas/repository.ex

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,45 @@ defmodule Algora.Workspace.Repository do
66

77
@derive {Inspect, except: [:provider_meta]}
88
typed_schema "repositories" do
9-
field :provider, :string
10-
field :provider_id, :string
11-
field :provider_meta, :map
9+
field :provider, :string, null: false
10+
field :provider_id, :string, null: false
11+
field :provider_meta, :map, null: false
1212

13-
field :name, :string
14-
field :url, :string
13+
field :name, :string, null: false
14+
field :url, :string, null: false
15+
field :description, :string
16+
field :og_image_url, :string, null: false
17+
field :og_image_updated_at, :utc_datetime_usec
1518

1619
has_many :tickets, Algora.Workspace.Ticket
17-
belongs_to :user, Algora.Accounts.User
20+
belongs_to :user, Algora.Accounts.User, null: false
1821

1922
timestamps()
2023
end
2124

25+
defp og_image_base_url, do: "https://opengraph.githubassets.com"
26+
27+
def has_default_og_image?(%Repository{} = repository),
28+
do: String.starts_with?(repository.og_image_url, og_image_base_url())
29+
30+
def default_og_image_url(repo_owner, repo_name), do: "#{og_image_base_url()}/0/#{repo_owner}/#{repo_name}"
31+
2232
def github_changeset(meta, user) do
2333
params = %{
2434
provider_id: to_string(meta["id"]),
2535
name: meta["name"],
36+
description: meta["description"],
37+
og_image_url: default_og_image_url(meta["owner"]["login"], meta["name"]),
38+
og_image_updated_at: DateTime.utc_now(),
2639
url: meta["html_url"],
2740
user_id: user.id
2841
}
2942

3043
%Repository{provider: "github", provider_meta: meta}
31-
|> cast(params, [:provider_id, :name, :url, :user_id])
44+
|> cast(params, [:provider_id, :name, :url, :description, :og_image_url, :og_image_updated_at, :user_id])
3245
|> generate_id()
3346
|> validate_required([:provider_id, :name, :url, :user_id])
47+
|> foreign_key_constraint(:user_id)
3448
|> unique_constraint([:provider, :provider_id])
3549
end
3650
end

lib/algora/workspace/workspace.ex

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ defmodule Algora.Workspace do
66
alias Algora.Github
77
alias Algora.Repo
88
alias Algora.Workspace.Installation
9+
alias Algora.Workspace.Jobs
910
alias Algora.Workspace.Repository
1011
alias Algora.Workspace.Ticket
1112

@@ -49,10 +50,34 @@ defmodule Algora.Workspace do
4950
where: u.provider_login == ^owner
5051
)
5152

52-
case Repo.one(repository_query) do
53-
%Repository{} = repository -> {:ok, repository}
54-
nil -> create_repository_from_github(token, owner, repo)
53+
res =
54+
case Repo.one(repository_query) do
55+
%Repository{} = repository -> {:ok, repository}
56+
nil -> create_repository_from_github(token, owner, repo)
57+
end
58+
59+
case res do
60+
{:ok, repository} -> maybe_schedule_og_image_update(repository)
61+
error -> error
62+
end
63+
64+
res
65+
end
66+
67+
defp maybe_schedule_og_image_update(%Repository{} = repository) do
68+
one_day_ago = DateTime.add(DateTime.utc_now(), -1, :day)
69+
70+
needs_update? =
71+
Repository.has_default_og_image?(repository) ||
72+
(repository.og_image_updated_at && DateTime.before?(repository.og_image_updated_at, one_day_ago))
73+
74+
if needs_update? do
75+
%{repository_id: repository.id}
76+
|> Jobs.UpdateRepositoryOgImage.new()
77+
|> Oban.insert()
5578
end
79+
80+
:ok
5681
end
5782

5883
def create_repository_from_github(token, owner, repo) do
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
defmodule Algora.Repo.Migrations.AddRepoFields do
2+
use Ecto.Migration
3+
4+
def change do
5+
alter table(:repositories) do
6+
add :description, :text
7+
add :og_image_url, :string
8+
add :og_image_updated_at, :utc_datetime_usec
9+
end
10+
11+
# Backfill existing repositories with default values
12+
execute """
13+
UPDATE repositories
14+
SET og_image_url = REPLACE(url, 'https://github.com', 'https://opengraph.githubassets.com/0')
15+
WHERE og_image_url IS NULL
16+
"""
17+
18+
# Make columns non-nullable after backfill
19+
alter table(:repositories) do
20+
modify :og_image_url, :string, null: false
21+
end
22+
end
23+
end

0 commit comments

Comments
 (0)