Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,5 @@ npm-debug.log
/priv/db
/priv/github
/priv/migration
/priv/dev
/priv/dev
/lib/algora_cloud*
3 changes: 3 additions & 0 deletions .iex.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ alias Algora.Contracts
alias Algora.Contracts.Contract
alias Algora.Contracts.Timesheet
alias Algora.Github
alias Algora.Jobs
alias Algora.Jobs.JobApplication
alias Algora.Jobs.JobPosting
alias Algora.Organizations
alias Algora.Organizations.Member
alias Algora.Payments
Expand Down
4 changes: 3 additions & 1 deletion assets/js/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,9 @@ const Hooks = {
},
DeriveDomain: {
mounted() {
const domainInput = document.querySelector("[data-domain-source]");
const domainInput = (this.el.closest("form") || document).querySelector(
"[data-domain-source]"
);
let shouldDerive = true;

// Listen for manual edits to the domain field
Expand Down
7 changes: 4 additions & 3 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import Config

config :algora,
title: "Algora",
description:
"Discover GitHub bounties, contract work and jobs. Hire the top 1% open source developers.",
description: "Algora connects companies and engineers for full-time and contract work",
ecto_repos: [Algora.Repo],
generators: [timestamp_type: :utc_datetime_usec],
redirects: [
Expand Down Expand Up @@ -57,7 +56,9 @@ config :algora, Oban,
activity_notifier: 1,
activity_mailer: 1,
activity_discord: 10,
campaign_emails: 1
campaign_emails: 1,
fetch_top_contributions: 1,
sync_contribution: 20
]

# Configures the mailer
Expand Down
2 changes: 2 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ config :algora, :local_store,
ttl: String.to_integer(System.get_env("LOCAL_STORE_TTL", "3600")),
salt: System.get_env("LOCAL_STORE_SALT", "algora-local-store")

config :algora, :discord, webhook_url: System.get_env("DISCORD_WEBHOOK_URL")

config :algora,
plausible_embed_url: System.get_env("PLAUSIBLE_EMBED_URL"),
assets_url: System.get_env("ASSETS_URL"),
Expand Down
3 changes: 3 additions & 0 deletions lib/algora/accounts/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ defmodule Algora.Accounts do
{:ids, ids}, query ->
from([b] in query, where: b.id in ^ids)

{:limit, :infinity}, query ->
query

{:limit, limit}, query ->
from([b] in query, limit: ^limit)

Expand Down
7 changes: 7 additions & 0 deletions lib/algora/accounts/schemas/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ defmodule Algora.Accounts.User do
field :priority, :integer, default: 0
field :fee_pct, :integer, default: 9
field :fee_pct_prev, :integer, default: 9
field :subscription_price, Money
field :seeded, :boolean, default: false
field :activated, :boolean, default: false
field :max_open_attempts, :integer, default: 3
Expand All @@ -51,6 +52,7 @@ defmodule Algora.Accounts.User do
field :seeking_contracts, :boolean, default: false
field :seeking_jobs, :boolean, default: false
field :hiring, :boolean, default: false
field :hiring_subscription, Ecto.Enum, values: [:inactive, :trial, :active], default: :inactive

field :hourly_rate_min, Money
field :hourly_rate_max, Money
Expand Down Expand Up @@ -81,6 +83,11 @@ defmodule Algora.Accounts.User do
field :login_token, :string, virtual: true
field :signup_token, :string, virtual: true

field :billing_name, :string
field :billing_address, :string
field :executive_name, :string
field :executive_role, :string

has_many :identities, Identity
has_many :memberships, Member, foreign_key: :user_id
has_many :members, Member, foreign_key: :org_id
Expand Down
7 changes: 6 additions & 1 deletion lib/algora/activities/activities.ex
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,12 @@ defmodule Algora.Activities do

discord_job =
if discord_payload = DiscordViews.render(activity) do
[Algora.Activities.SendDiscord.changeset(%{payload: discord_payload})]
[
Algora.Activities.SendDiscord.changeset(%{
url: Algora.config([:discord, :webhook_url]),
payload: discord_payload
})
]
else
[]
end
Expand Down
4 changes: 2 additions & 2 deletions lib/algora/activities/jobs/send_discord.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule Algora.Activities.SendDiscord do
end

@impl Oban.Worker
def perform(%Oban.Job{args: %{"payload" => payload}}) do
Algora.Discord.send_message(payload)
def perform(%Oban.Job{args: %{"url" => url, "payload" => payload}}) do
Algora.Discord.send_message(url, payload)
end
end
78 changes: 75 additions & 3 deletions lib/algora/admin/admin.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
defmodule Algora.Admin do
@moduledoc false
import Ecto.Changeset
import Ecto.Query

alias Algora.Accounts
Expand All @@ -10,6 +11,7 @@ defmodule Algora.Admin do
alias Algora.Bounties.Bounty
alias Algora.Bounties.Claim
alias Algora.Github
alias Algora.Jobs.JobPosting
alias Algora.Parser
alias Algora.Payments
alias Algora.Payments.Transaction
Expand All @@ -22,6 +24,71 @@ defmodule Algora.Admin do

require Logger

def seed_job(opts \\ %{}) do
with {:ok, user} <- Repo.fetch_by(User, handle: opts.org.handle),
{:ok, user} <- user |> change(opts.org) |> Repo.update(),
{:ok, job} <-
Repo.insert(%JobPosting{
id: Nanoid.generate(),
user_id: user.id,
company_name: user.name,
company_url: user.website_url,
title: opts.title,
description: opts.description,
tech_stack: opts.tech_stack || Enum.take(user.tech_stack, 1)
}) do
dbg("#{AlgoraWeb.Endpoint.url()}/#{user.handle}/jobs/#{job.id}")
end
end

def sync_contributions(opts \\ []) do
query =
User
|> where([u], not is_nil(u.handle))
|> where([u], not is_nil(u.provider_login))
|> where([u], u.type == :individual)
|> where([u], fragment("not exists (select 1 from user_contributions where user_contributions.user_id = ?)", u.id))

query =
if handles = opts[:handles] do
where(query, [u], u.handle in ^handles)
else
query
end

query =
if limit = opts[:limit] do
limit(query, ^limit)
else
query
end

Repo.transaction(
fn ->
if opts[:dry_run] do
query
|> Repo.stream()
|> Enum.to_list()
|> length()
|> IO.puts()
else
query
|> Repo.stream()
|> Enum.each(fn user ->
%{provider_login: user.provider_login}
|> Workspace.Jobs.FetchTopContributions.new()
|> Oban.insert()
|> case do
{:ok, _job} -> IO.puts("Enqueued job for #{user.provider_login}")
{:error, error} -> IO.puts("Failed to enqueue job for #{user.provider_login}: #{inspect(error)}")
end
end)
end
end,
timeout: :infinity
)
end

def release_payment(tx_id) do
Repo.transact(fn ->
{_, [tx]} =
Expand Down Expand Up @@ -402,6 +469,7 @@ defmodule Algora.Admin do
Logger.error(message)

%{
url: Algora.config([:discord, :webhook_url]),
payload: %{
embeds: [
%{
Expand All @@ -422,16 +490,19 @@ defmodule Algora.Admin do

email_job =
Algora.Activities.SendEmail.changeset(%{
title: "Error: #{message}",
title: "#{message}",
body: message,
name: "Algora Alert",
name: "Action required",
email: "[email protected]"
})

discord_job =
SendDiscord.changeset(%{
url: Algora.Settings.get("discord_webhook_url")["critical"] || Algora.config([:discord, :webhook_url]),
payload: %{
embeds: [%{color: color(severity), title: "Error", description: message, timestamp: DateTime.utc_now()}]
embeds: [
%{color: color(severity), title: "Action required", description: message, timestamp: DateTime.utc_now()}
]
}
})

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

%{
url: Algora.config([:discord, :webhook_url]),
payload: %{
embeds: [
%{
Expand Down
24 changes: 24 additions & 0 deletions lib/algora/cloud.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule Algora.Cloud do
@moduledoc false

def top_contributions(github_handle) do
call(AlgoraCloud, :top_contributions, [github_handle])
end

def list_top_matches(opts \\ []) do
call(AlgoraCloud, :list_top_matches, [opts])
end

def get_contribution_score(tech_stack, user, contributions_map) do
call(AlgoraCloud, :get_contribution_score, [tech_stack, user, contributions_map])
end

defp call(module, function, args) do
if :code.which(module) == :non_existing do
# TODO: call algora API
{:error, :not_loaded}
else
apply(module, function, args)
end
end
end
11 changes: 2 additions & 9 deletions lib/algora/integrations/discord/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,8 @@ defmodule Algora.Discord.Client do

require Logger

def webhook_url, do: Algora.config([:discord, :webhook_url])

def post(data) do
if url = webhook_url() do
do_post(url, data)
else
{:ok, nil}
end
end
def post(nil, _data), do: {:ok, nil}
def post(url, data), do: do_post(url, data)

defp do_post(url, data) do
headers = [{"Content-Type", "application/json"}]
Expand Down
12 changes: 5 additions & 7 deletions lib/algora/integrations/discord/discord.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ defmodule Algora.Discord do

require Logger

defdelegate webhook_url, to: Client

@doc """
Sends a message to a Discord channel.

Expand All @@ -21,10 +19,10 @@ defmodule Algora.Discord do

## Examples

iex> Discord.send_message(%{content: "Hello, world!"})
iex> Discord.send_message("https://discord.com/api/webhooks/1234567890/abcdefg", %{content: "Hello, world!"})
{:ok, response}

iex> Discord.send_message(%{
iex> Discord.send_message("https://discord.com/api/webhooks/1234567890/abcdefg", %{
...> embeds: [
...> %{
...> color: 0x6366f1,
Expand All @@ -41,15 +39,15 @@ defmodule Algora.Discord do
...> })
{:ok, response}
"""
@spec send_message(map()) :: {:ok, map() | nil} | {:error, any()}
def send_message(input) do
@spec send_message(String.t(), map()) :: {:ok, map() | nil} | {:error, any()}
def send_message(url, input) do
input =
Map.merge(
%{username: "Algora.io", avatar_url: "https://algora.io/asset/storage/v1/object/public/images/logo-256px.png"},
input
)

case Client.post(input) do
case Client.post(url, input) do
{:ok, response} ->
{:ok, response}

Expand Down
19 changes: 16 additions & 3 deletions lib/algora/integrations/github/token_pool.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ defmodule Algora.Github.TokenPool do
end

def get_token do
GenServer.call(__MODULE__, :get_token)
case maybe_get_token() do
token when is_binary(token) -> token
_ -> get_token()
end
end

def maybe_get_token do
GenServer.call(__MODULE__, :maybe_get_token)
end

def refresh_tokens do
Expand All @@ -35,7 +42,7 @@ defmodule Algora.Github.TokenPool do
end

@impl true
def handle_call(:get_token, _from, %{current_token_index: index, tokens: tokens} = state) do
def handle_call(:maybe_get_token, _from, %{current_token_index: index, tokens: tokens} = state) do
token = Enum.at(tokens, index)

if token == nil do
Expand All @@ -44,7 +51,13 @@ defmodule Algora.Github.TokenPool do
next_index = rem(index + 1, length(tokens))
if next_index == 0, do: refresh_tokens()

{:reply, token, %{state | current_token_index: next_index}}
case Github.get_current_user(token) do
{:ok, _} ->
{:reply, token, %{state | current_token_index: next_index}}

_ ->
{:reply, nil, %{state | current_token_index: next_index}}
end
end
end

Expand Down
Loading