Skip to content

Commit

Permalink
feat: add cv upload to users and improve home page layout
Browse files Browse the repository at this point in the history
  • Loading branch information
nunom27 committed Feb 9, 2025
1 parent c049277 commit 0796ce9
Show file tree
Hide file tree
Showing 15 changed files with 373 additions and 254 deletions.
45 changes: 22 additions & 23 deletions lib/safira/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,11 @@ defmodule Safira.Accounts do
end

@doc """
Lists all attendees with CV.
Lists all users with CV.
"""
def list_attendees_with_cv do
Attendee
|> where([at], not is_nil(at.cv))
|> preload(:user)
def list_users_with_cv do
User
|> where([user], not is_nil(user.cv))
|> Repo.all()
end

Expand Down Expand Up @@ -81,24 +80,6 @@ defmodule Safira.Accounts do
|> Repo.update()
end

@doc """
Updates an attendee's CV.
## Examples
iex> update_atttendee_cv(badge, %{cv: cv})
{:ok, %Badge{}}
iex> update_attendee_cv(badge, %{cv: bad_cv})
{:error, %Ecto.Changeset{}}
"""
def update_attendee_cv(%Attendee{} = attendee, attrs) do
attendee
|> Attendee.cv_changeset(attrs)
|> Repo.update()
end

def change_attendee(%Attendee{} = attendee, attrs \\ %{}) do
Attendee.changeset(attendee, attrs)
end
Expand Down Expand Up @@ -412,6 +393,24 @@ defmodule Safira.Accounts do
|> Repo.update()
end

@doc """
Updates a user's CV.
## Examples
iex> update_user_cv(user, %{cv: cv})
{:ok, %User{}}
iex> update_user_cv(user, %{cv: bad_cv})
{:error, %Ecto.Changeset{}}
"""
def update_user_cv(%User{} = user, attrs) do
user
|> User.cv_changeset(attrs)
|> Repo.update()
end

@doc """
Returns an `%Ecto.Changeset{}` for tracking user changes.
Expand Down
6 changes: 0 additions & 6 deletions lib/safira/accounts/attendee.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ defmodule Safira.Accounts.Attendee do
schema "attendees" do
field :tokens, :integer, default: 0
field :entries, :integer, default: 0
field :cv, Uploaders.CV.Type

belongs_to :course, Safira.Accounts.Course
belongs_to :user, Safira.Accounts.User
Expand All @@ -28,11 +27,6 @@ defmodule Safira.Accounts.Attendee do
|> validate_required(@required_fields)
end

def cv_changeset(attendee, attrs) do
attendee
|> cast_attachments(attrs, [:cv])
end

def update_tokens_changeset(attendee, attrs) do
attendee
|> cast(attrs, [:tokens])
Expand Down
7 changes: 7 additions & 0 deletions lib/safira/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ defmodule Safira.Accounts.User do
field :confirmed_at, :utc_datetime
field :type, Ecto.Enum, values: [:attendee, :staff, :company], default: :attendee
field :allows_marketing, :boolean, default: false
field :cv, Uploaders.CV.Type

has_one :attendee, Attendee, on_delete: :delete_all
has_one :staff, Staff, on_delete: :delete_all, on_replace: :update
Expand Down Expand Up @@ -269,4 +270,10 @@ defmodule Safira.Accounts.User do
user
|> cast_attachments(attrs, [:picture])
end

@doc false
def cv_changeset(attendee, attrs) do
attendee
|> cast_attachments(attrs, [:cv])
end
end
12 changes: 6 additions & 6 deletions lib/safira/companies.ex
Original file line number Diff line number Diff line change
Expand Up @@ -371,14 +371,14 @@ defmodule Safira.Companies do
"""
def get_cvs(company) when not is_nil(company.badge_id) do
if company.tier.full_cv_access do
Accounts.list_attendees_with_cv()
|> Enum.map(fn at ->
{at.user.handle, Uploaders.CV.url({at.cv, at}, :original, signed: true)}
Accounts.list_users_with_cv()
|> Enum.map(fn user ->
{user.handle, Uploaders.CV.url({user.cv, user}, :original, signed: true)}
end)
else
Contest.list_attendees_with_badge_and_cv(company.badge_id)
|> Enum.map(fn at ->
{at.user.handle, Uploaders.CV.url({at.cv, at}, :original, signed: true)}
Contest.list_users_with_badge_and_cv(company.badge_id)
|> Enum.map(fn user ->
{user.handle, Uploaders.CV.url({user.cv, user}, :original, signed: true)}
end)
end
end
Expand Down
37 changes: 26 additions & 11 deletions lib/safira/contest.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Safira.Contest do
use Safira.Context

alias Ecto.Multi
alias Safira.Accounts.Attendee
alias Safira.Accounts.{Attendee, User}
alias Safira.{Companies, Spotlights, Workers}

alias Safira.Contest.{
Expand Down Expand Up @@ -121,20 +121,35 @@ defmodule Safira.Contest do
end

@doc """
Lists all badge redeems belonging to a badge, where the attendee has uploaded their CV.
Lists all users (both attendees and staffs) that have the specified badge and have uploaded their CV.
## Examples
iex> list_attendees_with_badge_and_cv(123)
[%BadgeRedeem{}, %BadgeRedeem{}]
iex> list_users_with_badge_and_cv(123)
[%User{}, ...]
"""
def list_attendees_with_badge_and_cv(badge_id) do
Attendee
|> join(:inner, [at], br in BadgeRedeem, on: at.id == br.attendee_id)
|> where([at, br], br.badge_id == ^badge_id and not is_nil(at.cv))
|> preload(:user)
|> select([at, br], at)
def list_users_with_badge_and_cv(badge_id) do
# First query for attendees with badge and CV
attendees_query =
from u in User,
join: at in Attendee,
on: at.user_id == u.id,
join: br in BadgeRedeem,
on: br.attendee_id == at.id,
where: br.badge_id == ^badge_id and not is_nil(u.cv),
where: u.type == :attendee,
select: u.id

# Then query for staff with badge and CV
staff_query =
from u in User,
where: u.type == :staff and not is_nil(u.cv),
select: u.id

# Combine queries and preload associations after
User
|> where([u], u.id in subquery(attendees_query |> union(^staff_query)))
|> preload([:attendee, :staff])
|> Repo.all()
end

Expand Down
6 changes: 3 additions & 3 deletions lib/safira/uploaders/cv.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Safira.Uploaders.CV do

use Safira.Uploader

alias Safira.Accounts.Attendee
alias Safira.Accounts.User

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

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

def filename(version, _) do
Expand Down
123 changes: 123 additions & 0 deletions lib/safira_web/components/cv_upload.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
defmodule SafiraWeb.Components.CVUpload do
@moduledoc """
Attendee Curriculum Vitae upload component.
"""
use SafiraWeb, :live_component

alias Safira.Accounts
alias Safira.Uploaders.CV

import SafiraWeb.Components.ImageUploader
import SafiraWeb.Components.Button

attr :in_app, :boolean, default: false

@impl true
def render(assigns) do
~H"""
<div>
<.simple_form
for={@form}
id="attendee-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<div class="flex flex-col md:flex-row w-full gap-4">
<div class="w-full">
<.image_uploader class="h-40" upload={@uploads.cv}>
<:placeholder>
<div class="select-none flex flex-col gap-2 items-center text-lightMuted dark:text-darkMuted">
<.icon name="hero-arrow-up-tray" class="w-12 h-12" />
<p class="px-4 text-center">Upload your Curriculum Vitae</p>
</div>
</:placeholder>
</.image_uploader>
</div>
</div>
<:actions>
<%= if @in_app do %>
<.action_button title="Upload" phx-disable-with="Uploading..." />
<% else %>
<.button phx-disable-with="Uploading...">Upload</.button>
<% end %>
</:actions>
</.simple_form>
</div>
"""
end

@impl true
def mount(socket) do
{:ok,
socket
|> assign(:uploaded_files, [])
|> allow_upload(:cv,
accept: CV.extension_whitelist(),
max_entries: 1
)}
end

@impl true
def update(%{current_user: user} = assigns, socket) do
{:ok,
socket
|> assign(assigns)
|> assign_new(:form, fn ->
to_form(Accounts.change_user_profile(user))
end)}
end

@impl true
def handle_event("validate", %{"current_user" => user_params}, socket) do
changeset = Accounts.change_user_profile(socket.assigns.attendee, user_params)
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end

def handle_event("validate", _params, socket) do
{:noreply, socket}
end

def handle_event("save", %{"current_user" => user_params}, socket) do
save_user(socket, user_params)
end

def handle_event("save", %{}, socket) do
save_user(socket, %{})
end

defp save_user(socket, user_params) do
case Accounts.update_user(socket.assigns.current_user, user_params) do
{:ok, user} ->
case consume_pdf_data(user, socket) do
{:ok, _user} ->
{:noreply,
socket
|> put_flash(:info, "CV uploaded successfully.")
|> push_patch(to: socket.assigns.patch)}
end

{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end

defp consume_pdf_data(user, socket) do
consume_uploaded_entries(socket, :cv, fn %{path: path}, entry ->
Accounts.update_user_cv(user, %{
"cv" => %Plug.Upload{
content_type: entry.client_type,
filename: entry.client_name,
path: path
}
})
end)
|> case do
[{:ok, user}] ->
{:ok, user}

_errors ->
{:ok, user}
end
end
end
Loading

0 comments on commit 0796ce9

Please sign in to comment.