Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add detour notifications to notifications module #2813

Merged
merged 4 commits into from
Sep 26, 2024
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
112 changes: 111 additions & 1 deletion lib/notifications/db/detour.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,24 @@ defmodule Notifications.Db.Detour do
@derive {Jason.Encoder,
only: [
:__struct__,
:status
:status,
:headsign,
:route,
:direction,
:origin
]}

typed_schema "detour_notifications" do
belongs_to :detour, Skate.Detours.Db.Detour
has_one :notification, Notifications.Db.Notification

field :status, Ecto.Enum, values: [:activated]

# Derived from the associated detour
field :headsign, :any, virtual: true
field :route, :any, virtual: true
field :direction, :any, virtual: true
field :origin, :any, virtual: true
end

def changeset(
Expand All @@ -33,4 +43,104 @@ defmodule Notifications.Db.Detour do
:status
])
end

defmodule Queries do
firestack marked this conversation as resolved.
Show resolved Hide resolved
@moduledoc """
Defines composable queries for retrieving `Notifications.Db.Detour` info.
"""

import Ecto.Query

@doc """
The "base" query that queries `Notifications.Db.Detour`'s without restriction


## Examples

The `base` query returns all Detour Notifications

iex> :detour
...> |> insert()
...> |> Notifications.Notification.create_activated_detour_notification_from_detour()
...>
iex> all_detour_notifications =
...> Notifications.Db.Detour.Queries.base()
...> |> Skate.Repo.all()
...>
iex> match?(
...> [
...> %Notifications.Db.Detour{}
...> ],
...> all_detour_notifications
...> )
true

"""
def base() do
from(d in Notifications.Db.Detour, as: :detour_notification, select_merge: d)
end

@doc """
Retrieves detour information for notifications from the `Notifications.Db.Detour` table

## Examples

iex> :detour
...> |> insert()
...> |> Notifications.Notification.create_activated_detour_notification_from_detour()
...>
iex> all_detour_notifications =
...> Notifications.Db.Detour.Queries.get_derived_info()
...> |> Skate.Repo.all()
...>
iex> [
...> %Notifications.Db.Detour{
...> route: route,
...> origin: origin,
...> headsign: headsign,
...> direction: direction
...> }
...> ] = all_detour_notifications
iex> Enum.any?([route, origin, headsign, direction], &is_nil/1)
false

"""
def get_derived_info(query \\ base()) do
joshlarson marked this conversation as resolved.
Show resolved Hide resolved
from(
[detour_notification: dn] in query,
left_join: ad in assoc(dn, :detour),
as: :associated_detour,
select_merge: %{
route: ad.state["context"]["route"]["name"],
origin: ad.state["context"]["routePattern"]["name"],
headsign: ad.state["context"]["routePattern"]["headsign"],

# Ecto can't figure out how to index a JSON map via another JSON value
firestack marked this conversation as resolved.
Show resolved Hide resolved
# because (in the ways it was tried) Ecto won't allow us to use the
# value from the associated detour, `ad`, as a value in the
# ["JSON path"](https://hexdocs.pm/ecto/Ecto.Query.API.html#json_extract_path/2).
#
# i.e., this
# ad.state["context"]["route"]["directionNames"][
# ad.state["context"]["routePattern"]["directionId"]
# ]
#
# But, Postgres _is_ able to do this, _if_ we get the types correct.
# A JSON value in Postgres is either of type JSON or JSONB, but
# - indexing a JSON array requires an `INTEGER`,
# - accessing a JSON map, requires Postgres's `TEXT` type.
#
# So because we know the `directionId` will correspond to the keys in
# `directionNames`, casting the `directionId` to `TEXT` allows us to
# access the `directionNames` JSON map
direction:
fragment(
"? -> CAST(? AS TEXT)",
ad.state["context"]["route"]["directionNames"],
ad.state["context"]["routePattern"]["directionId"]
)
}
)
end
end
end
127 changes: 127 additions & 0 deletions lib/notifications/db/notification.ex
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,131 @@ defmodule Notifications.Db.Notification do
)
|> validate_required(:created_at)
end

defmodule Queries do
@moduledoc """
Composable queries for accessing `Notifications.Db.Notification`
related data
"""
import Ecto.Query

@doc """
The "base" query that queries `Notifications.Db.Notification`'s without restriction
"""
def base() do
from(n in Notifications.Db.Notification, as: :notification, select_merge: n)
end

def select_user_read_state(query \\ base(), user_id \\ nil) do
from([notification: n] in query,
join: nu in assoc(n, :notification_users),
as: :notification_user,
join: u in assoc(nu, :user),
as: :user,
where: u.id == ^user_id,
select_merge: %{
state: nu.state
}
)
end

@doc """
Joins associated `Notifications.Db.Detour`'s on
`Notifications.Db.Notification`'s and retrieves the Detour's
associated info.

## Examples

There is a `base` query struct that can be provided at the
beginning of a query:

iex> :detour
...> |> insert()
...> |> Notifications.Notification.create_activated_detour_notification_from_detour()
...>
iex> all_detour_notifications =
...> Notifications.Db.Notification.Queries.base()
...> |> Notifications.Db.Notification.Queries.select_detour_info()
...> |> Skate.Repo.all()
...> |> Skate.Repo.preload(:detour)
...>
iex> match?(
...> [
...> %Notifications.Db.Notification{
...> detour: %Notifications.Db.Detour{}
...> }
...> ], all_detour_notifications
...> )
true

If `base` is omitted, then it's inferred:

iex> :detour
...> |> insert()
...> |> Notifications.Notification.create_activated_detour_notification_from_detour()
...>
iex> all_detour_notifications =
...> Notifications.Db.Notification.Queries.select_detour_info()
...> |> Skate.Repo.all()
...> |> Skate.Repo.preload(:detour)
...>
iex> match?(
...> [
...> %Notifications.Db.Notification{
...> detour: %Notifications.Db.Detour{}
...> }
...> ],
...> all_detour_notifications
...> )
true

"""
@spec select_detour_info(Ecto.Query.t()) :: Ecto.Query.t()
@spec select_detour_info() :: Ecto.Query.t()
def select_detour_info(query \\ base()) do
from([notification: n] in query,
left_join: detour in subquery(Notifications.Db.Detour.Queries.get_derived_info()),
on: detour.id == n.detour_id,
select_merge: %{
detour: detour
}
)
end

@doc """
Joins associated `Notifications.Db.BridgeMovement`'s on
`Notifications.Db.Notification`'s
"""
@spec select_bridge_movements(Ecto.Query.t()) :: Ecto.Query.t()
def select_bridge_movements(query \\ base()) do
query
|> with_named_binding(:bridge_movement, fn query, binding ->
from([notification: n] in query,
left_join: bm in assoc(n, ^binding),
as: ^binding
)
end)
|> select_merge([bridge_movement: bm], %{
bridge_movement: bm
})
end

@doc """
Joins associated `Notifications.Db.BlockWaiver`'s on
`Notifications.Db.Notification`'s
"""
@spec select_block_waivers(Ecto.Query.t()) :: Ecto.Query.t()
def select_block_waivers(query \\ base()) do
query
|> with_named_binding(:block_waiver, fn query, binding ->
from([notification: n] in query,
left_join: bw in assoc(n, ^binding),
as: ^binding
)
end)
|> select_merge([block_waiver: bw], %{
block_waiver: bw
})
end
end
end
24 changes: 24 additions & 0 deletions lib/notifications/detour.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule Notifications.Detour do
@moduledoc """
Context for working with Detour notifications
"""

@doc """
Creates a detour notification struct from a detour to insert into the database
"""
def detour_notification(%Skate.Detours.Db.Detour{} = detour) do
%Notifications.Db.Detour{
detour: detour
}
end

@doc """
Creates a activated detour notification struct to insert into the database
"""
def activated_detour(%Skate.Detours.Db.Detour{} = detour) do
%{
detour_notification(detour)
| status: :activated
}
end
end
Loading
Loading