Skip to content

Commit

Permalink
Merge branch 'main' into dg/attendee_profile
Browse files Browse the repository at this point in the history
  • Loading branch information
joaodiaslobo authored Feb 11, 2025
2 parents b0b2368 + 082adf4 commit 68b6dd8
Show file tree
Hide file tree
Showing 73 changed files with 2,332 additions and 174 deletions.
13 changes: 9 additions & 4 deletions assets/js/hooks/qr_reading.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const QrScanner = {
mounted() {
const config = { fps: 2, qrbox: (width, height) => {return { width: width * 0.8, height: height * 0.9 }}};
this.scanner = new Html5Qrcode(this.el.id, { formatsToSupport: [ Html5QrcodeSupportedFormats.QR_CODE ] });
this.isScanning = false;

const onScanSuccess = (decodedText, decodedResult) => {
if (this.el.dataset.on_success) {
Expand All @@ -14,9 +15,11 @@ export const QrScanner = {
const startScanner = () => {
this.scanner.start({ facingMode: "environment" }, config, onScanSuccess)
.then((_) => {
this.isScanning = true;
if (this.el.dataset.on_start)
Function("hook", this.el.dataset.on_start)(this);
}, (e) => {
this.isScanning = false;
if (this.el.dataset.on_error)
Function("hook", this.el.dataset.on_error)(this);
});
Expand All @@ -31,9 +34,11 @@ export const QrScanner = {
},

destroyed() {
this.scanner.stop().then((_) => {
if (this.el.dataset.on_stop)
Function("hook", this.el.dataset.on_stop)(this);
});
if (this.isScanning) {
this.scanner.stop().then((_) => {
if (this.el.dataset.on_stop)
Function("hook", this.el.dataset.on_stop)(this);
});
}
}
}
3 changes: 2 additions & 1 deletion lib/safira/accounts/attendee.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ defmodule Safira.Accounts.Attendee do
use Safira.Schema

@required_fields ~w(user_id)a
@optional_fields ~w(tokens entries course_id)a
@optional_fields ~w(tokens entries course_id ineligible)a

schema "attendees" do
field :tokens, :integer, default: 0
field :entries, :integer, default: 0
field :ineligible, :boolean, default: false

belongs_to :course, Safira.Accounts.Course
belongs_to :user, Safira.Accounts.User
Expand Down
178 changes: 178 additions & 0 deletions lib/safira/contest.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule Safira.Contest do
BadgeCondition,
BadgeRedeem,
BadgeTrigger,
DailyPrize,
DailyTokens
}

Expand Down Expand Up @@ -342,6 +343,100 @@ defmodule Safira.Contest do
Repo.delete(badge)
end

@doc """
Lists all daily prizes
## Examples
iex> list_prizes()
[%DailyPrize{}, %DailyPrize{}]
"""
def list_daily_prizes do
DailyPrize
|> order_by([dp], asc: fragment("? NULLS FIRST", dp.date), asc: dp.place)
|> Repo.all()
|> Repo.preload([:prize])
end

@doc """
Gets a single daily prize.
Raises `Ecto.NoResultsError` if the daily prize does not exist.
## Examples
iex> get_daily_prize!(123)
%Badge{}
iex> get_daily_prize!(456)
** (Ecto.NoResultsError)
"""
def get_daily_prize!(id) do
Repo.get!(DailyPrize, id)
end

@doc """
Creates a daily prize.
## Examples
iex> create_daily_prize(%{field: value})
{:ok, %DailyPrize{}}
iex> create_daily_prize(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_daily_prize(attrs \\ %{}) do
%DailyPrize{}
|> DailyPrize.changeset(attrs)
|> Repo.insert()
end

@doc """
Updates a daily_prize.
## Examples
iex> update_daily_prize(daily_prize, %{field: new_value})
{:ok, %DailyPrize{}}
iex> update_daily_prize(daily_prize, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_daily_prize(%DailyPrize{} = daily_prize, attrs \\ %{}) do
daily_prize
|> DailyPrize.changeset(attrs)
|> Repo.update()
end

@doc """
Returns an `%Ecto.Changeset{}` for tracking daily prize changes.
## Examples
iex> change_daily_prize()
%Ecto.Changeset{data: %Badge{}}
"""
def change_daily_prize(%DailyPrize{} = daily_prize, attrs \\ %{}) do
DailyPrize.changeset(daily_prize, attrs)
end

@doc """
Deletes a daily prize.
## Examples
iex> delete_daily_prize(daily_prize)
{:ok, %DailyPrize{}}
"""
def delete_daily_prize(%DailyPrize{} = daily_prize) do
Repo.delete(daily_prize)
end

@doc """
Gets the company associated with a badge.
Expand Down Expand Up @@ -553,6 +648,89 @@ defmodule Safira.Contest do
|> Repo.transaction()
end

@doc """
Gets the top ranking attendees in a given day
## Examples
iex> leaderboard(~D[2025-02-10], 10)
[%{attendee_id: id, position: 1, name: John Doe, tokens: 10, badges: 20}, ...]
"""
def leaderboard(day, limit \\ 10) do
daily_leaderboard_query(day)
|> limit(^limit)
|> presentation_query()
|> Repo.all()
end

@doc """
Gets the position of the given attendee on the daily leaderboard
## Examples
iex> leaderboard_position(~D[2025-02-10], id)
%{attendee_id: id, position: 1, name: John Doe, tokens: 10, badges: 20}
"""
def leaderboard_position(day, attendee_id) do
daily_leaderboard_query(day)
|> presentation_query()
|> subquery()
|> where([u], u.attendee_id == ^attendee_id)
|> Repo.one()
end

defp daily_leaderboard_query(day) do
daily_leaderboard_tokens_query(day)
|> join(:inner, [dt], rd in subquery(daily_leaderboard_redeem_query(day)),
on: dt.attendee_id == rd.attendee_id
)
|> sort_query()
end

defp daily_leaderboard_redeem_query(day) do
day_time = DateTime.new!(day, ~T[00:00:00], "Etc/UTC")
start_time = Timex.beginning_of_day(day_time)
end_time = Timex.end_of_day(day_time)

BadgeRedeem
|> join(:inner, [rd], b in Badge, on: rd.badge_id == b.id)
|> where([rd], rd.inserted_at >= ^start_time and rd.inserted_at <= ^end_time)
|> where([rd, b], b.counts_for_day)
|> group_by([rd], rd.attendee_id)
|> select([rd], %{redeem_count: count(rd.id), attendee_id: rd.attendee_id})
end

defp daily_leaderboard_tokens_query(day) do
start_time = Timex.beginning_of_day(day)
end_time = Timex.end_of_day(day)

DailyTokens
|> where([dt], dt.date >= ^start_time and dt.date <= ^end_time)
end

defp sort_query(query) do
query
|> order_by([dt, rd], desc: rd.redeem_count, desc: dt.tokens)
end

defp presentation_query(query) do
query
|> join(:inner, [dt, rd], at in Safira.Accounts.Attendee, on: at.id == rd.attendee_id)
|> join(:inner, [dt, rd, at], u in Safira.Accounts.User, on: u.id == at.user_id)
|> where([dt, rd, at, u], not at.ineligible)
|> select([dt, rd, at, u], %{
attendee_id: at.id,
position:
fragment("row_number() OVER (ORDER BY ? DESC, ? DESC)", rd.redeem_count, dt.tokens),
name: u.name,
handle: u.handle,
badges: rd.redeem_count,
tokens: dt.tokens
})
end

@doc """
Gets the attendee token balance.
Expand Down
26 changes: 26 additions & 0 deletions lib/safira/contest/daily_prize.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Safira.Contest.DailyPrize do
@moduledoc """
General prizes, that can be won by having more badges and tokens at the end
of a day or of the whole event
"""

use Safira.Schema

@required_fields ~w(prize_id date place)a

schema "daily_prizes" do
belongs_to :prize, Safira.Minigames.Prize
field :date, :date
field :place, :integer

timestamps()
end

@doc false
def changeset(daily_prize, attrs) do
daily_prize
|> cast(attrs, @required_fields)
|> validate_required(@required_fields)
|> unique_constraint([:date, :place])
end
end
2 changes: 1 addition & 1 deletion lib/safira/contest/daily_tokens.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ defmodule Safira.Contest.DailyTokens do
def changeset(daily_tokens, attrs) do
daily_tokens
|> cast(attrs, @required_fields)
|> validate_required(@required_fields)
|> foreign_key_constraint(:attendee_id)
|> validate_number(:tokens, greater_than_or_equal_to: 0)
|> validate_required(@required_fields)
|> unique_constraint([:date, :attendee_id])
end
end
15 changes: 15 additions & 0 deletions lib/safira/event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,21 @@ defmodule Safira.Event do
end
end

@doc """
Returns the list of dates between the event's start and end dates.
## Examples
iex> list_event_dates()
[~D[2025-02-11], ~D[2025-02-12], ~D[2025-02-13], ~D[2025-02-14]]
"""
def list_event_dates do
start_date = get_event_start_date()
end_date = get_event_end_date()

Date.range(start_date, end_date) |> Enum.to_list()
end

@doc """
Changes the event's start date.
Expand Down
13 changes: 12 additions & 1 deletion lib/safira/inventory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule Safira.Inventory do
@moduledoc """
The Inventory context.
"""
use Safira.Context

import Ecto.Query, warn: false
alias Safira.Repo
Expand All @@ -21,6 +22,12 @@ defmodule Safira.Inventory do
Repo.all(Item)
end

def list_items(opts) when is_list(opts) do
Item
|> apply_filters(opts)
|> Repo.all()
end

@doc """
Gets a single item.
Expand All @@ -35,7 +42,11 @@ defmodule Safira.Inventory do
** (Ecto.NoResultsError)
"""
def get_item!(id), do: Repo.get!(Item, id)
def get_item!(id) do
Item
|> preload([:product, :attendee])
|> Repo.get!(id)
end

@doc """
Creates a item.
Expand Down
15 changes: 15 additions & 0 deletions lib/safira/inventory/item.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ defmodule Safira.Inventory.Item do
"""
use Safira.Schema

@derive {
Flop.Schema,
filterable: [:product_name],
sortable: [:redeemed_at, :inserted_at],
default_limit: 11,
join_fields: [
product_name: [
binding: :product,
field: :name,
path: [:product, :name],
ecto_type: :string
]
]
}

@required_fields ~w(type attendee_id)a
@optional_fields ~w(redeemed_at product_id prize_id staff_id)a

Expand Down
Loading

0 comments on commit 68b6dd8

Please sign in to comment.