Skip to content

Commit

Permalink
Add ability to create, update, and delete job posts
Browse files Browse the repository at this point in the history
This PR implements the ability for members to create, update and
delete job posts. It introduces the following important changes:

* Creates a `job_posts` database table.
* Creates a `job_posts_hisory` database table for tracking updates on
the `job_posts` table.
* Creates a `Jobs` context to house the functionality related to job
posts.
* Creates a `Jobs.Post` Ecto schema.
* Creates a `Jobs.PostHistoryEntry` Ecto schema.
* Creates a `JobController` which allows a user to create, update and
delete their own job posts.
* Adds a basic authorization logic in the context and controller.
* Adds a section in the basic member profile for managing job posts.
* Adds notification logic after a post is created.

This PR also introduces some minor changes which can be viewed as
placeholders for later iterations:

* Adds a top navigation entry for "Jobs".
* Adds a job posts index page where approved job posts are listed.
* Adds a mechanism for approving job posts by administrators.
* Adds notification logic after a post is approved.
* Associates members with sponsors.
  • Loading branch information
krasenyp authored and starbelly committed Jul 21, 2023
1 parent 153cfb8 commit 57a586e
Show file tree
Hide file tree
Showing 53 changed files with 2,023 additions and 158 deletions.
5 changes: 5 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ config :ex_aws,
host: "ewr1.vultrobjects.com"
]

config :erlef, Oban,
repo: Erlef.Repo,
plugins: [Oban.Plugins.Pruner],
queues: [default: 10]

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
5 changes: 2 additions & 3 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ config :erlef, :env, :test

config :erlef, Erlef.Mailer, adapter: Swoosh.Adapters.Test

config :erlef, :wild_apricot_base_api_url, "http://127.0.0.1:9999"
config :erlef, :wild_apricot_base_auth_url, "http://127.0.0.1:9999"

# Print only warnings and errors during test
config :logger, level: :warn

Expand Down Expand Up @@ -56,4 +53,6 @@ config :honeybadger,
api_key: "test",
exclude_envs: [:test]

config :erlef, Oban, testing: :inline

config :erlef, api_key: "key"
13 changes: 7 additions & 6 deletions lib/erlef/accounts/external.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
defmodule Erlef.Accounts.External do
@moduledoc """
Erlef.Accounts.External provides a schema and helper functions for converting external accounts to an Erlef app
member or member attributes.
Erlef.Accounts.External provides a schema and helper functions for converting external accounts to an Erlef app
member or member attributes.
While this module is called External to leave it open, the only supported external account is Wildapricot.
While this module is called External to leave it open, the only supported external account is Wildapricot.
The module is intended to serve as boundary between the accounts context and wildapricot data structures.
Expand All @@ -26,7 +26,7 @@ defmodule Erlef.Accounts.External do

# The following is used to map wildapricot fields to normalized atoms for use
# within the app. Likewise,it used to construct lists that are in turn used to map this struct back to
# the wildapricot format. Note that the structure below is flat, where as the wildapricot data
# the wildapricot format. Note that the structure below is flat, where as the wildapricot data
# structure is not, in particular the "FieldValues" section of a contact is a list of maps.
#
# Per the above we annotate fields that are part of the "FieldValues" list.
Expand All @@ -51,8 +51,8 @@ defmodule Erlef.Accounts.External do
"has_email_box" => {:has_email_box, :field_value}
}

# We build these lists so that we don't have to compute them at runtime.
# They are used for mapping Member fields back to wildapricot fields and forms.
# We build these lists so that we don't have to compute them at runtime.
# They are used for mapping Member fields back to wildapricot fields and forms.
@field_value_key_map Enum.filter(@str_key_map, fn {_k, v} -> is_tuple(v) end)

@membership_level_str_map %{
Expand All @@ -61,6 +61,7 @@ defmodule Erlef.Accounts.External do
"Lifetime Supporting Membership" => :lifetime,
"Board" => :board,
"Fellow" => :fellow,
"Sponsored Membership" => :sponsored,
"Managing and Contributing" => :contributor
}

Expand Down
39 changes: 28 additions & 11 deletions lib/erlef/accounts/member.ex
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
defmodule Erlef.Accounts.Member do
use Erlef.Schema

alias Erlef.Jobs.Post
alias Erlef.Groups.Sponsor

@moduledoc """
Erlef.Accounts.Member provides a schema and helper functions for working with erlef members.
Members are a "concrete" representation of an associated external resource, namely wildapricot,
Members are a "concrete" representation of an associated external resource, namely wildapricot,
and as such it shou ld be duly that this application is not the source of truth of members.
The schema allows us to cache member attributes, such as the member's name. This is useful in the case of
quite wildapricot per their strict api rate limits. Additionally, this allows us to properly associate and
constraint other schemas within the system to a member; as well as keeping the rest of the application
completely ignorant in regards to wildapricot
The schema allows us to cache member attributes, such as the member's name. This is useful in the case of
quite wildapricot per their strict api rate limits. Additionally, this allows us to properly associate and
constraint other schemas within the system to a member; as well as keeping the rest of the application
completely ignorant in regards to wildapricot
See `Erlef.Accounts.External` for details on how fields are mapped between the two resources.
"""
Expand All @@ -21,11 +24,13 @@ defmodule Erlef.Accounts.Member do
:lifetime,
:board,
:fellow,
:contributor
:contributor,
:sponsored
]

@membership_level_str_map %{
"Basic Membership" => :basic,
"Sponsored Membership" => :sponsored,
"Annual Supporting Membership" => :annual,
"Lifetime Supporting Membership" => :lifetime,
"Board" => :board,
Expand Down Expand Up @@ -59,7 +64,12 @@ defmodule Erlef.Accounts.Member do
field(:has_requested_slack_invite, :boolean, default: false)
field(:requested_slack_invite_at, :utc_datetime)
field(:deactivated_at, :utc_datetime)

embeds_one(:external, Erlef.Accounts.External, on_replace: :update)

belongs_to(:sponsor, Sponsor)
has_many(:posts, Post, foreign_key: :created_by)

timestamps()
end

Expand All @@ -84,7 +94,8 @@ defmodule Erlef.Accounts.Member do
:requested_slack_invite_at,
:suspended_member,
:terms_of_use_accepted,
:deactivated_at
:deactivated_at,
:sponsor_id
]

@required_fields [
Expand Down Expand Up @@ -116,10 +127,16 @@ defmodule Erlef.Accounts.Member do
|> validate_email(:erlef_email_address)
end

def by_external_id(id) do
from(m in __MODULE__,
where: fragment("(external->>'id' = ?)", ^id)
)
def from(query \\ __MODULE__) do
from(query, as: :member)
end

def where_sponsor_id(query \\ from(), sponsor_id) do
where(query, [member: m], m.sponsor_id == ^sponsor_id)
end

def by_external_id(query \\ from(), external_id) do
where(query, [member: m], fragment("(?->>'id' = ?)", m.external, ^external_id))
end

def is_paying?(%Member{membership_level: level}) when is_atom(level) do
Expand Down
5 changes: 4 additions & 1 deletion lib/erlef/admins.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ defmodule Erlef.Admins do
(select count(id) from volunteers),
(select count(id) from working_groups),
(select count(id) from sponsors),
(select count(id) from events where events.approved = false),
(select count(id) from events where events.approved = false),
(select count(id) from job_posts where job_posts.approved = false),
(select count(id) from member_email_requests where member_email_requests.status != 'complete'),
(select count(id) from apps)
"""
Expand All @@ -44,6 +45,7 @@ defmodule Erlef.Admins do
working_group_count,
sponsors_count,
unapproved_events_count,
unapproved_job_posts_count,
outstanding_email_requests_count,
apps_count
]
Expand All @@ -55,6 +57,7 @@ defmodule Erlef.Admins do
working_groups_count: working_group_count,
sponsors_count: sponsors_count,
unapproved_events_count: unapproved_events_count,
unapproved_job_posts_count: unapproved_job_posts_count,
outstanding_email_requests_count: outstanding_email_requests_count,
apps_count: apps_count
}
Expand Down
15 changes: 14 additions & 1 deletion lib/erlef/admins/notifications.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ defmodule Erlef.Admins.Notifications do

import Swoosh.Email

@type notification_type() :: :new_email_request | :new_event_submitted | :new_slack_invite
@type notification_type() ::
:new_email_request | :new_event_submitted | :new_slack_invite | :new_job_post_submitted

@type params() :: map()

Expand Down Expand Up @@ -31,4 +32,16 @@ defmodule Erlef.Admins.Notifications do
|> subject("A new event was submitted")
|> text_body(msg)
end

def new_job_post_submission() do
msg = """
A new job post was submitted. Visit https://erlef.org/admin/ to view unapproved events.
"""

new()
|> to({"Website Admins", "[email protected]"})
|> from({"Erlef Notifications", "[email protected]"})
|> subject("A new job post was successfully submitted.")
|> text_body(msg)
end
end
1 change: 1 addition & 0 deletions lib/erlef/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ defmodule Erlef.Application do
defp base_children() do
[
Erlef.Repo,
{Oban, Application.fetch_env!(:erlef, Oban)},
Erlef.Repo.ETS,
Erlef.Repo.ETS.Importer,
ErlefWeb.Telemetry,
Expand Down
6 changes: 6 additions & 0 deletions lib/erlef/groups/sponsor.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
defmodule Erlef.Groups.Sponsor do
@moduledoc false

use Erlef.Schema

alias Erlef.Accounts.Member

schema "sponsors" do
field(:active, :boolean, default: true)
field(:logo, :string, virtual: true)
Expand All @@ -12,6 +15,9 @@ defmodule Erlef.Groups.Sponsor do
field(:created_by, Ecto.UUID)
field(:updated_by, Ecto.UUID)

has_many(:members, Member)
has_many(:posts, through: [:members, :posts])

timestamps()
end

Expand Down
Loading

0 comments on commit 57a586e

Please sign in to comment.