Skip to content

Commit 0536ca1

Browse files
Darguimanunom27
andauthored
feat: app home page (#442)
Co-authored-by: Nuno Miguel <[email protected]>
1 parent 082adf4 commit 0536ca1

File tree

68 files changed

+2392
-725
lines changed

Some content is hidden

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

68 files changed

+2392
-725
lines changed

lib/safira/accounts.ex

+127-24
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,11 @@ defmodule Safira.Accounts do
4747
end
4848

4949
@doc """
50-
Lists all attendees with CV.
50+
Lists all users with CV.
5151
"""
52-
def list_attendees_with_cv do
53-
Attendee
54-
|> where([at], not is_nil(at.cv))
55-
|> preload(:user)
52+
def list_users_with_cv do
53+
User
54+
|> where([user], not is_nil(user.cv))
5655
|> Repo.all()
5756
end
5857

@@ -81,24 +80,6 @@ defmodule Safira.Accounts do
8180
|> Repo.update()
8281
end
8382

84-
@doc """
85-
Updates an attendee's CV.
86-
87-
## Examples
88-
89-
iex> update_atttendee_cv(badge, %{cv: cv})
90-
{:ok, %Badge{}}
91-
92-
iex> update_attendee_cv(badge, %{cv: bad_cv})
93-
{:error, %Ecto.Changeset{}}
94-
95-
"""
96-
def update_attendee_cv(%Attendee{} = attendee, attrs) do
97-
attendee
98-
|> Attendee.cv_changeset(attrs)
99-
|> Repo.update()
100-
end
101-
10283
def change_attendee(%Attendee{} = attendee, attrs \\ %{}) do
10384
Attendee.changeset(attendee, attrs)
10485
end
@@ -211,6 +192,24 @@ defmodule Safira.Accounts do
211192
Repo.get_by(User, email: email)
212193
end
213194

195+
@doc """
196+
Gets a user by handle.
197+
198+
## Examples
199+
200+
iex> get_user_by_handle("lisasimpson")
201+
%User{}
202+
203+
iex> get_user_by_handle("lisasimpson")
204+
nil
205+
206+
"""
207+
def get_user_by_handle!(handle) when is_binary(handle) do
208+
User
209+
|> preload([:attendee])
210+
|> Repo.get_by!(handle: handle)
211+
end
212+
214213
@doc """
215214
Gets a user by email and password.
216215
@@ -338,6 +337,109 @@ defmodule Safira.Accounts do
338337
|> Repo.insert()
339338
end
340339

340+
@doc """
341+
Returns an `%Ecto.Changeset{}` for changing the user profile (name, handle, password and email).
342+
Doesn't validate the uniqueness of the email.
343+
344+
## Examples
345+
346+
iex> change_user_profile(user)
347+
%Ecto.Changeset{data: %User{}}
348+
349+
"""
350+
def change_user_profile(user, attrs \\ %{}) do
351+
user
352+
|> User.profile_changeset(attrs, validate_email: false)
353+
|> User.picture_changeset(attrs)
354+
end
355+
356+
@doc """
357+
Updates the user profile (name, handle, password).
358+
359+
If everything succeed, emulates that the email was change without
360+
actually changing it in the database.
361+
"""
362+
def update_user_profile(%User{} = user, current_password, attrs) do
363+
password_changed? =
364+
attrs["password"] != nil && String.trim(attrs["password"]) != ""
365+
366+
changeset =
367+
user
368+
|> User.profile_changeset(attrs, validate_email: true)
369+
|> maybe_validate_current_password(password_changed?, current_password)
370+
371+
# Just simulate a complete update, to check if everything is valid
372+
applied_user = Ecto.Changeset.apply_action(changeset, :update)
373+
374+
case applied_user do
375+
{:ok, _} ->
376+
# Removing the email from the changeset, since the mail just will be updated over mail confirmation
377+
changeset_without_mail_update = Ecto.Changeset.change(changeset, email: user.email)
378+
379+
tokens_to_delete = if password_changed?, do: :all, else: ["false"]
380+
381+
Ecto.Multi.new()
382+
|> Ecto.Multi.update(:user, changeset_without_mail_update)
383+
# The tokens will just be deleted if the password was changed
384+
|> Ecto.Multi.delete_all(
385+
:tokens,
386+
UserToken.by_user_and_contexts_query(user, tokens_to_delete)
387+
)
388+
|> Repo.transaction()
389+
|> case do
390+
# Return the user with ALL the changes
391+
{:ok, _} -> applied_user
392+
{:error, :user, changeset, _} -> {:error, changeset}
393+
end
394+
395+
otherwise ->
396+
otherwise
397+
end
398+
end
399+
400+
defp maybe_validate_current_password(changeset, password_changed?, current_password) do
401+
if password_changed? do
402+
User.validate_current_password(changeset, current_password)
403+
else
404+
changeset
405+
end
406+
end
407+
408+
def update_user_picture(%User{} = user, attrs) do
409+
user
410+
|> User.picture_changeset(attrs)
411+
|> Repo.update()
412+
end
413+
414+
@doc """
415+
Updates a user's CV.
416+
417+
## Examples
418+
419+
iex> update_user_cv(user, %{cv: cv})
420+
{:ok, %User{}}
421+
422+
iex> update_user_cv(user, %{cv: bad_cv})
423+
{:error, %Ecto.Changeset{}}
424+
425+
"""
426+
def update_user_cv(%User{} = user, attrs) do
427+
user
428+
|> User.cv_changeset(attrs)
429+
|> Repo.update()
430+
|> case do
431+
{:ok, user} ->
432+
if user.type == :attendee do
433+
Contest.enqueue_badge_trigger_execution_job(user.attendee, :upload_cv_event)
434+
end
435+
436+
{:ok, user}
437+
438+
{:error, changeset} ->
439+
{:error, changeset}
440+
end
441+
end
442+
341443
@doc """
342444
Returns an `%Ecto.Changeset{}` for tracking user changes.
343445
@@ -807,12 +909,13 @@ defmodule Safira.Accounts do
807909
iex> get_attendee_from_credential(456)
808910
nil
809911
"""
810-
def get_attendee_from_credential(credential_id) do
912+
def get_attendee_from_credential(credential_id, preloads \\ []) do
811913
Credential
812914
|> where([c], c.id == ^credential_id)
813915
|> join(:inner, [c], a in assoc(c, :attendee))
814916
|> select([c, a], a)
815917
|> Repo.one()
918+
|> Repo.preload(preloads)
816919
end
817920

818921
@doc """

lib/safira/accounts/attendee.ex

-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ defmodule Safira.Accounts.Attendee do
1010
schema "attendees" do
1111
field :tokens, :integer, default: 0
1212
field :entries, :integer, default: 0
13-
field :cv, Uploaders.CV.Type
1413
field :ineligible, :boolean, default: false
1514

1615
belongs_to :course, Safira.Accounts.Course
@@ -29,11 +28,6 @@ defmodule Safira.Accounts.Attendee do
2928
|> validate_required(@required_fields)
3029
end
3130

32-
def cv_changeset(attendee, attrs) do
33-
attendee
34-
|> cast_attachments(attrs, [:cv])
35-
end
36-
3731
def update_tokens_changeset(attendee, attrs) do
3832
attendee
3933
|> cast(attrs, [:tokens])

lib/safira/accounts/user.ex

+31-5
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@ defmodule Safira.Accounts.User do
3636
field :name, :string
3737
field :email, :string
3838
field :handle, :string
39+
field :picture, Safira.Uploaders.UserPicture.Type
3940
field :password, :string, virtual: true, redact: true
4041
field :hashed_password, :string, redact: true
4142
field :current_password, :string, virtual: true, redact: true
4243
field :confirmed_at, :utc_datetime
4344
field :type, Ecto.Enum, values: [:attendee, :staff, :company], default: :attendee
4445
field :allows_marketing, :boolean, default: false
46+
field :cv, Uploaders.CV.Type
4547

4648
has_one :attendee, Attendee, on_delete: :delete_all
4749
has_one :staff, Staff, on_delete: :delete_all, on_replace: :update
@@ -103,6 +105,7 @@ defmodule Safira.Accounts.User do
103105
|> cast(attrs, [:name, :handle, :email, :confirmed_at, :type])
104106
|> unique_constraint(:email)
105107
|> validate_handle()
108+
|> email_changeset(attrs, opts |> Keyword.put(:check_email_changed, false))
106109
|> if_changed_password_changeset(attrs, opts)
107110
end
108111

@@ -177,15 +180,26 @@ defmodule Safira.Accounts.User do
177180
@doc """
178181
A user changeset for changing the email.
179182
180-
It requires the email to change otherwise an error is added.
183+
## Options
184+
185+
* `:check_email_changed` - If true, it requires the email to change, otherwise an error is added.
186+
Defaults to `true`.
181187
"""
182188
def email_changeset(user, attrs, opts \\ []) do
183189
user
184190
|> cast(attrs, [:email])
185191
|> validate_email(opts)
186-
|> case do
187-
%{changes: %{email: _}} = changeset -> changeset
188-
%{} = changeset -> add_error(changeset, :email, "did not change")
192+
|> maybe_validate_email_changed(opts)
193+
end
194+
195+
defp maybe_validate_email_changed(%{changes: %{email: _}} = changeset, _opts), do: changeset
196+
197+
defp maybe_validate_email_changed(changeset, opts) do
198+
check_email_changed? = Keyword.get(opts, :check_email_changed, true)
199+
200+
case check_email_changed? do
201+
true -> changeset |> add_error(:email, "did not change")
202+
false -> changeset
189203
end
190204
end
191205

@@ -247,7 +261,19 @@ defmodule Safira.Accounts.User do
247261
if valid_password?(changeset.data, password) do
248262
changeset
249263
else
250-
add_error(changeset, :current_password, "is not valid")
264+
add_error(changeset, :current_password, "password not correct")
251265
end
252266
end
267+
268+
@doc false
269+
def picture_changeset(user, attrs) do
270+
user
271+
|> cast_attachments(attrs, [:picture])
272+
end
273+
274+
@doc false
275+
def cv_changeset(attendee, attrs) do
276+
attendee
277+
|> cast_attachments(attrs, [:cv])
278+
end
253279
end

lib/safira/companies.ex

+6-6
Original file line numberDiff line numberDiff line change
@@ -371,14 +371,14 @@ defmodule Safira.Companies do
371371
"""
372372
def get_cvs(company) when not is_nil(company.badge_id) do
373373
if company.tier.full_cv_access do
374-
Accounts.list_attendees_with_cv()
375-
|> Enum.map(fn at ->
376-
{at.user.handle, Uploaders.CV.url({at.cv, at}, :original, signed: true)}
374+
Accounts.list_users_with_cv()
375+
|> Enum.map(fn user ->
376+
{user.handle, Uploaders.CV.url({user.cv, user}, :original, signed: true)}
377377
end)
378378
else
379-
Contest.list_attendees_with_badge_and_cv(company.badge_id)
380-
|> Enum.map(fn at ->
381-
{at.user.handle, Uploaders.CV.url({at.cv, at}, :original, signed: true)}
379+
Contest.list_users_with_badge_and_cv(company.badge_id)
380+
|> Enum.map(fn user ->
381+
{user.handle, Uploaders.CV.url({user.cv, user}, :original, signed: true)}
382382
end)
383383
end
384384
end

lib/safira/contest.ex

+26-11
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule Safira.Contest do
55
use Safira.Context
66

77
alias Ecto.Multi
8-
alias Safira.Accounts.Attendee
8+
alias Safira.Accounts.{Attendee, User}
99
alias Safira.{Companies, Spotlights, Workers}
1010

1111
alias Safira.Contest.{
@@ -122,20 +122,35 @@ defmodule Safira.Contest do
122122
end
123123

124124
@doc """
125-
Lists all badge redeems belonging to a badge, where the attendee has uploaded their CV.
125+
Lists all users (both attendees and staffs) that have the specified badge and have uploaded their CV.
126126
127127
## Examples
128128
129-
iex> list_attendees_with_badge_and_cv(123)
130-
[%BadgeRedeem{}, %BadgeRedeem{}]
131-
129+
iex> list_users_with_badge_and_cv(123)
130+
[%User{}, ...]
132131
"""
133-
def list_attendees_with_badge_and_cv(badge_id) do
134-
Attendee
135-
|> join(:inner, [at], br in BadgeRedeem, on: at.id == br.attendee_id)
136-
|> where([at, br], br.badge_id == ^badge_id and not is_nil(at.cv))
137-
|> preload(:user)
138-
|> select([at, br], at)
132+
def list_users_with_badge_and_cv(badge_id) do
133+
# First query for attendees with badge and CV
134+
attendees_query =
135+
from u in User,
136+
join: at in Attendee,
137+
on: at.user_id == u.id,
138+
join: br in BadgeRedeem,
139+
on: br.attendee_id == at.id,
140+
where: br.badge_id == ^badge_id and not is_nil(u.cv),
141+
where: u.type == :attendee,
142+
select: u.id
143+
144+
# Then query for staff with badge and CV
145+
staff_query =
146+
from u in User,
147+
where: u.type == :staff and not is_nil(u.cv),
148+
select: u.id
149+
150+
# Combine queries and preload associations after
151+
User
152+
|> where([u], u.id in subquery(attendees_query |> union(^staff_query)))
153+
|> preload([:attendee, :staff])
139154
|> Repo.all()
140155
end
141156

lib/safira/contest/badge.ex

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule Safira.Contest.Badge do
77
alias Safira.Companies
88

99
@required_fields ~w(name description begin end tokens entries category_id)a
10-
@optional_fields ~w(image counts_for_day givable)a
10+
@optional_fields ~w(image counts_for_day givable is_checkpoint)a
1111

1212
@derive {
1313
Flop.Schema,
@@ -24,6 +24,7 @@ defmodule Safira.Contest.Badge do
2424
field :end, :utc_datetime
2525
field :counts_for_day, :boolean, default: true
2626
field :givable, :boolean, default: true
27+
field :is_checkpoint, :boolean, default: false
2728

2829
belongs_to :category, Safira.Contest.BadgeCategory
2930
has_one :company, Companies.Company

lib/safira/uploaders/cv.ex

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule Safira.Uploaders.CV do
55

66
use Safira.Uploader
77

8-
alias Safira.Accounts.Attendee
8+
alias Safira.Accounts.User
99

1010
@versions [:original]
1111
@extension_whitelist ~w(.pdf)
@@ -15,8 +15,8 @@ defmodule Safira.Uploaders.CV do
1515
Enum.member?(extension_whitelist(), file_extension)
1616
end
1717

18-
def storage_dir(_, {_file, %Attendee{} = attendee}) do
19-
"uploads/cvs/attendee/#{attendee.id}"
18+
def storage_dir(_, {_file, %User{} = user}) do
19+
"uploads/user/cv/#{user.id}"
2020
end
2121

2222
def filename(version, _) do

0 commit comments

Comments
 (0)