Skip to content

Commit 5852c19

Browse files
committed
upgrade airtable service + add custom component
1 parent e43fb52 commit 5852c19

File tree

8 files changed

+526
-9
lines changed

8 files changed

+526
-9
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
AIRTABLE_API_KEY=api-key
2+
AIRTABLE_BASE_ID=app***
3+
AIRTABLE_TABLE_NAME=tb***

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,5 @@ elixir_playground-*.tar
3535
npm-debug.log
3636
/assets/node_modules/
3737

38-
39-
.env*
38+
.env
4039

config/runtime.exs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ if File.exists?(".env") do
2727
source!([".env"])
2828
end
2929

30-
config :elixir_playground, :airtable, api_key: env!("AIRTABLE_API_KEY", :string)
30+
config :elixir_playground, :airtable,
31+
api_key: env!("AIRTABLE_API_KEY", :string),
32+
base_id: env!("AIRTABLE_BASE_ID", :string),
33+
table_name: env!("AIRTABLE_TABLE_NAME", :string)
3134

3235
if config_env() == :prod do
3336
database_url =

lib/elixir_playground/airtable.ex

Lines changed: 137 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
defmodule ElixirPlayground.Airtable do
2-
@base_id "appCmysegOYgrUMsE"
3-
@table_name "tblILZeZPnCFsgxd4"
4-
@url "https://api.airtable.com/v0/#{@base_id}/#{@table_name}"
5-
62
@default_page_size 100
73

84
def list_records(opts \\ []) do
9-
api_key = Application.get_env(:elixir_playground, :airtable)[:api_key]
5+
config = Application.get_env(:elixir_playground, :airtable)
6+
base_id = config[:base_id]
7+
table_name = config[:table_name]
8+
api_key = config[:api_key]
109

10+
base_url = "https://api.airtable.com/v0/#{base_id}/#{table_name}"
1111
IO.inspect(api_key)
1212

1313
headers = [
@@ -18,7 +18,7 @@ defmodule ElixirPlayground.Airtable do
1818
offset = Keyword.get(opts, :offset, nil)
1919
page_size = Keyword.get(opts, :page_size, @default_page_size)
2020

21-
url = build_url(@url, offset: offset, page_size: page_size)
21+
url = build_url(base_url, offset: offset, page_size: page_size)
2222
IO.puts("Requesting Airtable API: #{url}")
2323
request = Finch.build(:get, url, headers)
2424

@@ -34,6 +34,130 @@ defmodule ElixirPlayground.Airtable do
3434
end
3535
end
3636

37+
def get_record(record_id) do
38+
config = Application.get_env(:elixir_playground, :airtable)
39+
base_id = config[:base_id]
40+
table_name = config[:table_name]
41+
api_key = config[:api_key]
42+
43+
url = "https://api.airtable.com/v0/#{base_id}/#{table_name}/#{record_id}"
44+
headers = build_headers(api_key)
45+
46+
IO.puts("Getting record from Airtable: #{url}")
47+
request = Finch.build(:get, url, headers)
48+
49+
case Finch.request(request, ElixirPlayground.Finch) do
50+
{:ok, %Finch.Response{status: 200, body: body}} ->
51+
{:ok, Jason.decode!(body)}
52+
53+
{:ok, %Finch.Response{status: 404, body: _body}} ->
54+
{:error, :not_found}
55+
56+
{:ok, %Finch.Response{status: status, body: body}} ->
57+
{:error, %{status: status, body: Jason.decode!(body)}}
58+
59+
{:error, reason} ->
60+
{:error, reason}
61+
end
62+
end
63+
64+
def update_record(record_id, fields) do
65+
config = Application.get_env(:elixir_playground, :airtable)
66+
base_id = config[:base_id]
67+
table_name = config[:table_name]
68+
api_key = config[:api_key]
69+
70+
url = "https://api.airtable.com/v0/#{base_id}/#{table_name}/#{record_id}"
71+
headers = build_headers(api_key)
72+
73+
body =
74+
%{
75+
"fields" => fields
76+
}
77+
|> Jason.encode!()
78+
79+
IO.puts("Updating record in Airtable: #{url}")
80+
request = Finch.build(:patch, url, headers, body)
81+
82+
case Finch.request(request, ElixirPlayground.Finch) do
83+
{:ok, %Finch.Response{status: 200, body: response_body}} ->
84+
{:ok, Jason.decode!(response_body)}
85+
86+
{:ok, %Finch.Response{status: 404, body: _body}} ->
87+
{:error, :not_found}
88+
89+
{:ok, %Finch.Response{status: 422, body: response_body}} ->
90+
{:error, %{status: 422, type: :validation_error, body: Jason.decode!(response_body)}}
91+
92+
{:ok, %Finch.Response{status: status, body: response_body}} ->
93+
{:error, %{status: status, body: Jason.decode!(response_body)}}
94+
95+
{:error, reason} ->
96+
{:error, reason}
97+
end
98+
end
99+
100+
def delete_record(record_id) do
101+
config = Application.get_env(:elixir_playground, :airtable)
102+
base_id = config[:base_id]
103+
table_name = config[:table_name]
104+
api_key = config[:api_key]
105+
106+
url = "https://api.airtable.com/v0/#{base_id}/#{table_name}/#{record_id}"
107+
headers = build_headers(api_key)
108+
109+
IO.puts("Deleting record from Airtable: #{url}")
110+
request = Finch.build(:delete, url, headers)
111+
112+
case Finch.request(request, ElixirPlayground.Finch) do
113+
{:ok, %Finch.Response{status: 200, body: response_body}} ->
114+
{:ok, Jason.decode!(response_body)}
115+
116+
{:ok, %Finch.Response{status: 404, body: _body}} ->
117+
{:error, :not_found}
118+
119+
{:ok, %Finch.Response{status: status, body: response_body}} ->
120+
{:error, %{status: status, body: Jason.decode!(response_body)}}
121+
122+
{:error, reason} ->
123+
{:error, reason}
124+
end
125+
end
126+
127+
# Bonus: Create a new record
128+
def create_record(fields) do
129+
config = Application.get_env(:elixir_playground, :airtable)
130+
base_id = config[:base_id]
131+
table_name = config[:table_name]
132+
api_key = config[:api_key]
133+
134+
url = "https://api.airtable.com/v0/#{base_id}/#{table_name}"
135+
headers = build_headers(api_key)
136+
137+
body =
138+
%{
139+
"fields" => fields
140+
}
141+
|> Jason.encode!()
142+
143+
IO.puts("Creating record in Airtable: #{url}")
144+
request = Finch.build(:post, url, headers, body)
145+
146+
case Finch.request(request, ElixirPlayground.Finch) do
147+
{:ok, %Finch.Response{status: 200, body: response_body}} ->
148+
{:ok, Jason.decode!(response_body)}
149+
150+
{:ok, %Finch.Response{status: 422, body: response_body}} ->
151+
{:error, %{status: 422, type: :validation_error, body: Jason.decode!(response_body)}}
152+
153+
{:ok, %Finch.Response{status: status, body: response_body}} ->
154+
{:error, %{status: status, body: Jason.decode!(response_body)}}
155+
156+
{:error, reason} ->
157+
{:error, reason}
158+
end
159+
end
160+
37161
defp build_url(base, opts) do
38162
query_p =
39163
[
@@ -48,4 +172,11 @@ defmodule ElixirPlayground.Airtable do
48172
params -> base <> "?" <> URI.encode_query(params)
49173
end
50174
end
175+
176+
defp build_headers(api_key) do
177+
[
178+
{"Authorization", "Bearer #{api_key}"},
179+
{"Content-Type", "application/json"}
180+
]
181+
end
51182
end

lib/elixir_playground_web/components/core_components.ex

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,4 +469,66 @@ defmodule ElixirPlaygroundWeb.CoreComponents do
469469
def translate_errors(errors, field) when is_list(errors) do
470470
for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts})
471471
end
472+
473+
attr :current_page, :integer, default: nil
474+
attr :show_page_label, :boolean, default: false
475+
attr :page_size, :integer, default: nil
476+
attr :has_prev, :boolean, required: true
477+
attr :has_next, :boolean, required: true
478+
attr :prev_event, :string, default: "prev"
479+
attr :next_event, :string, default: "next"
480+
attr :prev_label, :string, default: "Prev"
481+
attr :next_label, :string, default: "Next"
482+
attr :size, :string, default: "btn-sm", values: ~w(btn-xs btn-sm btn-md btn-lg btn-xl)
483+
attr :class, :string, default: nil
484+
attr :rest, :global
485+
486+
def pagination(assigns) do
487+
assigns =
488+
assigns
489+
|> assign_new(:show_page_label, fn ->
490+
Map.get(assigns, :show_page_label) || not is_nil(assigns[:current_page])
491+
end)
492+
493+
~H"""
494+
<div class={["flex items-center justify-between gap-3", @class]} {@rest}>
495+
<div :if={@page_size} class="text-sm text-black opacity-70">
496+
Page size: {@page_size}
497+
</div>
498+
499+
<div class="join">
500+
<button
501+
class={["join-item btn", @size]}
502+
phx-click={@prev_event}
503+
disabled={!@has_prev}
504+
type="button"
505+
>
506+
{@prev_label}
507+
</button>
508+
509+
<button
510+
:if={@show_page_label}
511+
class={["join-item btn", @size, "btn-ghost"]}
512+
type="button"
513+
disabled
514+
>
515+
<%= if @current_page do %>
516+
Page {@current_page}
517+
<% else %>
518+
519+
<% end %>
520+
</button>
521+
522+
<button
523+
class={["join-item btn", @size]}
524+
phx-click={@next_event}
525+
disabled={!@has_next}
526+
type="button"
527+
>
528+
{@next_label}
529+
</button>
530+
</div>
531+
</div>
532+
"""
533+
end
472534
end

0 commit comments

Comments
 (0)