Skip to content

Commit 4797e7a

Browse files
committed
feat: job applications
1 parent 548ef59 commit 4797e7a

File tree

6 files changed

+103
-3
lines changed

6 files changed

+103
-3
lines changed

lib/algora/jobs/jobs.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ defmodule Algora.Jobs do
66

77
alias Algora.Accounts
88
alias Algora.Bounties.LineItem
9+
alias Algora.Jobs.JobApplication
910
alias Algora.Jobs.JobPosting
1011
alias Algora.Payments
1112
alias Algora.Payments.Transaction
@@ -92,4 +93,18 @@ defmodule Algora.Jobs do
9293
end
9394
end)
9495
end
96+
97+
def create_application(job_id, user) do
98+
%JobApplication{}
99+
|> JobApplication.changeset(%{job_id: job_id, user_id: user.id})
100+
|> Repo.insert()
101+
end
102+
103+
def list_user_applications(user) do
104+
JobApplication
105+
|> where([a], a.user_id == ^user.id)
106+
|> select([a], a.job_id)
107+
|> Repo.all()
108+
|> MapSet.new()
109+
end
95110
end
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
defmodule Algora.Jobs.JobApplication do
2+
@moduledoc false
3+
use Algora.Schema
4+
5+
alias Algora.Accounts.User
6+
alias Algora.Jobs.JobPosting
7+
8+
typed_schema "job_applications" do
9+
field :status, Ecto.Enum, values: [:pending], null: false, default: :pending
10+
11+
belongs_to :job, JobPosting, null: false
12+
belongs_to :user, User, null: false
13+
14+
timestamps()
15+
end
16+
17+
def changeset(job_application, attrs) do
18+
job_application
19+
|> cast(attrs, [:status, :job_id, :user_id])
20+
|> generate_id()
21+
|> validate_required([:status, :job_id, :user_id])
22+
|> unique_constraint([:job_id, :user_id])
23+
|> foreign_key_constraint(:job_id)
24+
|> foreign_key_constraint(:user_id)
25+
end
26+
end

lib/algora/jobs/schemas/job_posting.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ defmodule Algora.Jobs.JobPosting do
1515
field :status, Ecto.Enum, values: [:initialized, :processing, :active, :expired], null: false, default: :initialized
1616
field :expires_at, :utc_datetime_usec
1717

18-
belongs_to :user, User
18+
belongs_to :user, User, null: false
1919

2020
timestamps()
2121
end

lib/algora_web/live/jobs_live.ex

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ defmodule AlgoraWeb.JobsLive do
22
@moduledoc false
33
use AlgoraWeb, :live_view
44

5+
alias Algora.Accounts
56
alias Algora.Jobs
67
alias Algora.Jobs.JobPosting
78

@@ -16,7 +17,8 @@ defmodule AlgoraWeb.JobsLive do
1617
socket
1718
|> assign(:page_title, "Jobs")
1819
|> assign(:jobs, jobs)
19-
|> assign(:form, to_form(changeset))}
20+
|> assign(:form, to_form(changeset))
21+
|> assign_user_applications()}
2022
end
2123

2224
@impl true
@@ -64,6 +66,15 @@ defmodule AlgoraWeb.JobsLive do
6466
</div>
6567
</div>
6668
</div>
69+
<%= if MapSet.member?(@user_applications, job.id) do %>
70+
<.button disabled class="opacity-50">
71+
<.icon name="tabler-check" class="h-4 w-4 mr-2 -ml-1" /> Applied
72+
</.button>
73+
<% else %>
74+
<.button phx-click="apply_job" phx-value-job-id={job.id}>
75+
<.icon name="github" class="h-4 w-4 mr-2" /> Apply with GitHub
76+
</.button>
77+
<% end %>
6778
</div>
6879
<div class="text-sm text-muted-foreground">
6980
{job.description}
@@ -142,4 +153,34 @@ defmodule AlgoraWeb.JobsLive do
142153
{:noreply, assign(socket, :form, to_form(changeset))}
143154
end
144155
end
156+
157+
@impl true
158+
def handle_event("apply_job", %{"job-id" => job_id}, socket) do
159+
if socket.assigns[:current_user] do
160+
if Accounts.has_fresh_token?(socket.assigns.current_user) do
161+
case Jobs.create_application(job_id, socket.assigns.current_user) do
162+
{:ok, _application} ->
163+
{:noreply, assign_user_applications(socket)}
164+
165+
{:error, _changeset} ->
166+
{:noreply, put_flash(socket, :error, "Failed to submit application. Please try again.")}
167+
end
168+
else
169+
{:noreply, redirect(socket, external: Algora.Github.authorize_url())}
170+
end
171+
else
172+
{:noreply, redirect(socket, external: Algora.Github.authorize_url())}
173+
end
174+
end
175+
176+
defp assign_user_applications(socket) do
177+
user_applications =
178+
if socket.assigns[:current_user] do
179+
Jobs.list_user_applications(socket.assigns.current_user)
180+
else
181+
MapSet.new()
182+
end
183+
184+
assign(socket, :user_applications, user_applications)
185+
end
145186
end

priv/repo/migrations/20250424152036_create_job_postings.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ defmodule Algora.Repo.Migrations.CreateJobPostings do
1313
add :email, :string, null: false
1414
add :status, :string, null: false, default: "initialized"
1515
add :expires_at, :utc_datetime_usec
16-
add :user_id, references(:users, type: :string, on_delete: :restrict)
16+
add :user_id, references(:users, type: :string, on_delete: :restrict), null: false
1717

1818
timestamps()
1919
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
defmodule Algora.Repo.Migrations.CreateJobApplications do
2+
use Ecto.Migration
3+
4+
def change do
5+
create table(:job_applications, primary_key: false) do
6+
add :id, :string, primary_key: true
7+
add :status, :string, null: false, default: "pending"
8+
add :job_id, references(:job_postings, type: :string, on_delete: :restrict), null: false
9+
add :user_id, references(:users, type: :string, on_delete: :restrict), null: false
10+
11+
timestamps()
12+
end
13+
14+
create index(:job_applications, [:job_id])
15+
create index(:job_applications, [:user_id])
16+
create unique_index(:job_applications, [:job_id, :user_id])
17+
end
18+
end

0 commit comments

Comments
 (0)