Skip to content

Commit 217d31b

Browse files
authored
feat: hiring (job postings, screening, matching, profiles) (#127)
1 parent b1acd56 commit 217d31b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+6148
-2458
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,5 @@ npm-debug.log
5858
/priv/db
5959
/priv/github
6060
/priv/migration
61-
/priv/dev
61+
/priv/dev
62+
/lib/algora_cloud*

.iex.exs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ alias Algora.Contracts
1515
alias Algora.Contracts.Contract
1616
alias Algora.Contracts.Timesheet
1717
alias Algora.Github
18+
alias Algora.Jobs
19+
alias Algora.Jobs.JobApplication
20+
alias Algora.Jobs.JobPosting
1821
alias Algora.Organizations
1922
alias Algora.Organizations.Member
2023
alias Algora.Payments

assets/js/app.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,9 @@ const Hooks = {
389389
},
390390
DeriveDomain: {
391391
mounted() {
392-
const domainInput = document.querySelector("[data-domain-source]");
392+
const domainInput = (this.el.closest("form") || document).querySelector(
393+
"[data-domain-source]"
394+
);
393395
let shouldDerive = true;
394396

395397
// Listen for manual edits to the domain field

config/config.exs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import Config
99

1010
config :algora,
1111
title: "Algora",
12-
description:
13-
"Discover GitHub bounties, contract work and jobs. Hire the top 1% open source developers.",
12+
description: "Algora connects companies and engineers for full-time and contract work",
1413
ecto_repos: [Algora.Repo],
1514
generators: [timestamp_type: :utc_datetime_usec],
1615
redirects: [
@@ -57,7 +56,9 @@ config :algora, Oban,
5756
activity_notifier: 1,
5857
activity_mailer: 1,
5958
activity_discord: 10,
60-
campaign_emails: 1
59+
campaign_emails: 1,
60+
fetch_top_contributions: 1,
61+
sync_contribution: 20
6162
]
6263

6364
# Configures the mailer

config/test.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ config :algora, :local_store,
6464
ttl: String.to_integer(System.get_env("LOCAL_STORE_TTL", "3600")),
6565
salt: System.get_env("LOCAL_STORE_SALT", "algora-local-store")
6666

67+
config :algora, :discord, webhook_url: System.get_env("DISCORD_WEBHOOK_URL")
68+
6769
config :algora,
6870
plausible_embed_url: System.get_env("PLAUSIBLE_EMBED_URL"),
6971
assets_url: System.get_env("ASSETS_URL"),

lib/algora/accounts/accounts.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ defmodule Algora.Accounts do
4343
{:ids, ids}, query ->
4444
from([b] in query, where: b.id in ^ids)
4545

46+
{:limit, :infinity}, query ->
47+
query
48+
4649
{:limit, limit}, query ->
4750
from([b] in query, limit: ^limit)
4851

lib/algora/accounts/schemas/user.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ defmodule Algora.Accounts.User do
4040
field :priority, :integer, default: 0
4141
field :fee_pct, :integer, default: 9
4242
field :fee_pct_prev, :integer, default: 9
43+
field :subscription_price, Money
4344
field :seeded, :boolean, default: false
4445
field :activated, :boolean, default: false
4546
field :max_open_attempts, :integer, default: 3
@@ -51,6 +52,7 @@ defmodule Algora.Accounts.User do
5152
field :seeking_contracts, :boolean, default: false
5253
field :seeking_jobs, :boolean, default: false
5354
field :hiring, :boolean, default: false
55+
field :hiring_subscription, Ecto.Enum, values: [:inactive, :trial, :active], default: :inactive
5456

5557
field :hourly_rate_min, Money
5658
field :hourly_rate_max, Money
@@ -81,6 +83,11 @@ defmodule Algora.Accounts.User do
8183
field :login_token, :string, virtual: true
8284
field :signup_token, :string, virtual: true
8385

86+
field :billing_name, :string
87+
field :billing_address, :string
88+
field :executive_name, :string
89+
field :executive_role, :string
90+
8491
has_many :identities, Identity
8592
has_many :memberships, Member, foreign_key: :user_id
8693
has_many :members, Member, foreign_key: :org_id

lib/algora/activities/activities.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,12 @@ defmodule Algora.Activities do
300300

301301
discord_job =
302302
if discord_payload = DiscordViews.render(activity) do
303-
[Algora.Activities.SendDiscord.changeset(%{payload: discord_payload})]
303+
[
304+
Algora.Activities.SendDiscord.changeset(%{
305+
url: Algora.config([:discord, :webhook_url]),
306+
payload: discord_payload
307+
})
308+
]
304309
else
305310
[]
306311
end

lib/algora/activities/jobs/send_discord.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule Algora.Activities.SendDiscord do
1111
end
1212

1313
@impl Oban.Worker
14-
def perform(%Oban.Job{args: %{"payload" => payload}}) do
15-
Algora.Discord.send_message(payload)
14+
def perform(%Oban.Job{args: %{"url" => url, "payload" => payload}}) do
15+
Algora.Discord.send_message(url, payload)
1616
end
1717
end

lib/algora/admin/admin.ex

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule Algora.Admin do
22
@moduledoc false
3+
import Ecto.Changeset
34
import Ecto.Query
45

56
alias Algora.Accounts
@@ -10,6 +11,7 @@ defmodule Algora.Admin do
1011
alias Algora.Bounties.Bounty
1112
alias Algora.Bounties.Claim
1213
alias Algora.Github
14+
alias Algora.Jobs.JobPosting
1315
alias Algora.Parser
1416
alias Algora.Payments
1517
alias Algora.Payments.Transaction
@@ -22,6 +24,71 @@ defmodule Algora.Admin do
2224

2325
require Logger
2426

27+
def seed_job(opts \\ %{}) do
28+
with {:ok, user} <- Repo.fetch_by(User, handle: opts.org.handle),
29+
{:ok, user} <- user |> change(opts.org) |> Repo.update(),
30+
{:ok, job} <-
31+
Repo.insert(%JobPosting{
32+
id: Nanoid.generate(),
33+
user_id: user.id,
34+
company_name: user.name,
35+
company_url: user.website_url,
36+
title: opts.title,
37+
description: opts.description,
38+
tech_stack: opts.tech_stack || Enum.take(user.tech_stack, 1)
39+
}) do
40+
dbg("#{AlgoraWeb.Endpoint.url()}/#{user.handle}/jobs/#{job.id}")
41+
end
42+
end
43+
44+
def sync_contributions(opts \\ []) do
45+
query =
46+
User
47+
|> where([u], not is_nil(u.handle))
48+
|> where([u], not is_nil(u.provider_login))
49+
|> where([u], u.type == :individual)
50+
|> where([u], fragment("not exists (select 1 from user_contributions where user_contributions.user_id = ?)", u.id))
51+
52+
query =
53+
if handles = opts[:handles] do
54+
where(query, [u], u.handle in ^handles)
55+
else
56+
query
57+
end
58+
59+
query =
60+
if limit = opts[:limit] do
61+
limit(query, ^limit)
62+
else
63+
query
64+
end
65+
66+
Repo.transaction(
67+
fn ->
68+
if opts[:dry_run] do
69+
query
70+
|> Repo.stream()
71+
|> Enum.to_list()
72+
|> length()
73+
|> IO.puts()
74+
else
75+
query
76+
|> Repo.stream()
77+
|> Enum.each(fn user ->
78+
%{provider_login: user.provider_login}
79+
|> Workspace.Jobs.FetchTopContributions.new()
80+
|> Oban.insert()
81+
|> case do
82+
{:ok, _job} -> IO.puts("Enqueued job for #{user.provider_login}")
83+
{:error, error} -> IO.puts("Failed to enqueue job for #{user.provider_login}: #{inspect(error)}")
84+
end
85+
end)
86+
end
87+
end,
88+
timeout: :infinity
89+
)
90+
end
91+
2592
def release_payment(tx_id) do
2693
Repo.transact(fn ->
2794
{_, [tx]} =
@@ -402,6 +469,7 @@ defmodule Algora.Admin do
402469
Logger.error(message)
403470

404471
%{
472+
url: Algora.config([:discord, :webhook_url]),
405473
payload: %{
406474
embeds: [
407475
%{
@@ -422,16 +490,19 @@ defmodule Algora.Admin do
422490

423491
email_job =
424492
Algora.Activities.SendEmail.changeset(%{
425-
title: "Error: #{message}",
493+
title: "#{message}",
426494
body: message,
427-
name: "Algora Alert",
495+
name: "Action required",
428496
429497
})
430498

431499
discord_job =
432500
SendDiscord.changeset(%{
501+
url: Algora.Settings.get("discord_webhook_url")["critical"] || Algora.config([:discord, :webhook_url]),
433502
payload: %{
434-
embeds: [%{color: color(severity), title: "Error", description: message, timestamp: DateTime.utc_now()}]
503+
embeds: [
504+
%{color: color(severity), title: "Action required", description: message, timestamp: DateTime.utc_now()}
505+
]
435506
}
436507
})
437508

@@ -442,6 +513,7 @@ defmodule Algora.Admin do
442513
Logger.info(message)
443514

444515
%{
516+
url: Algora.config([:discord, :webhook_url]),
445517
payload: %{
446518
embeds: [
447519
%{

0 commit comments

Comments
 (0)