lib/code_corps/model/project.ex
defmodule CodeCorps.Project do
@moduledoc """
Represents a project on Code Corps, e.g. the "Code Corps" project itself.
"""
use CodeCorps.Model
import CodeCorps.Helpers.RandomIconColor
import CodeCorps.Helpers.Slug
import CodeCorps.Helpers.URL, only: [prefix_url: 2]
import CodeCorps.Validators.SlugValidator
import Ecto.Query, only: [where: 3]
alias CodeCorps.{Category, Repo, Skill, TaskList}
alias CodeCorps.Services.MarkdownRendererService
alias Ecto.Changeset
@type t :: %__MODULE__{}
schema "projects" do
field :approval_requested, :boolean, default: false
field :approved, :boolean, default: false
field :cloudinary_public_id
field :default_color
field :description, :string
field :long_description_body, :string
field :long_description_markdown, :string
field :should_link_externally, :boolean, default: false # temporary for linking to projects off Code Corps while in alpha
field :slug, :string
field :title, :string
field :total_monthly_donated, :integer, default: 0
field :website, :string
belongs_to :organization, CodeCorps.Organization
has_one :stripe_connect_plan, CodeCorps.StripeConnectPlan
has_many :donation_goals, CodeCorps.DonationGoal
has_many :github_repos, CodeCorps.GithubRepo
has_many :project_categories, CodeCorps.ProjectCategory
has_many :project_skills, CodeCorps.ProjectSkill
has_many :project_users, CodeCorps.ProjectUser
has_many :task_lists, CodeCorps.TaskList
has_many :tasks, CodeCorps.Task
has_many :stripe_connect_subscriptions, through: [:stripe_connect_plan, :stripe_connect_subscriptions]
many_to_many :categories, CodeCorps.Category, join_through: CodeCorps.ProjectCategory
many_to_many :skills, CodeCorps.Skill, join_through: CodeCorps.ProjectSkill
timestamps()
end
def changeset(struct, params) do
struct
|> cast(params, [:approval_requested, :cloudinary_public_id, :default_color, :description, :long_description_markdown, :title, :website])
|> prefix_url(:website)
|> validate_format(:website, CodeCorps.Helpers.URL.valid_format())
|> validate_required([:title])
|> generate_slug(:title, :slug)
|> validate_slug(:slug)
|> unique_constraint(:slug, name: :index_projects_on_slug)
|> check_constraint(:long_description_markdown,
message: "cannot be deleted once your project is approved",
name: "set_long_description_markdown_if_approved")
|> MarkdownRendererService.render_markdown_to_html(:long_description_markdown, :long_description_body)
end
@doc """
Builds a changeset for creating a project.
"""
def create_changeset(struct, params) do
struct
|> changeset(params)
|> cast(params, [:organization_id])
|> validate_required([:cloudinary_public_id, :description])
|> associate_categories(params)
|> validate_length(:categories, min: 1)
|> associate_skills(params)
|> validate_length(:skills, min: 1)
|> put_assoc(:task_lists, TaskList.default_task_lists())
|> put_member_assoc()
|> generate_icon_color(:default_color)
|> assoc_constraint(:organization)
end
defp associate_categories(changeset, %{"categories_ids" => ids}) when is_list(ids) when length(ids) > 0 do
categories = ids |> Enum.map(&String.to_integer/1) |> find_categories()
changeset |> put_assoc(:categories, categories)
end
defp associate_categories(changeset, _) do
changeset |> put_assoc(:categories, [])
end
defp find_categories(ids) do
Category
|> where([object], object.id in ^ids)
|> Repo.all()
end
defp associate_skills(changeset, %{"skills_ids" => ids}) when is_list(ids) when length(ids) > 0 do
skills = ids |> Enum.map(&String.to_integer/1) |> find_skills()
changeset |> put_assoc(:skills, skills)
end
defp associate_skills(changeset, _) do
changeset |> put_assoc(:skills, [])
end
defp find_skills(ids) do
Skill
|> where([object], object.id in ^ids)
|> Repo.all()
end
@doc """
Builds a changeset for updating a project.
"""
def update_changeset(struct, params) do
struct
|> changeset(params)
|> cast(params, [:approved])
end
def update_total_changeset(struct, params) do
struct
|> cast(params, [:total_monthly_donated])
end
@spec put_member_assoc(Changeset.t) :: Changeset.t
defp put_member_assoc(changeset) do
case changeset |> get_change(:organization_id) |> get_organization do
nil ->
changeset
organization ->
changeset
|> put_assoc(:project_users, [%{user_id: organization.owner_id, role: "owner"}])
end
end
@spec get_organization(integer | nil) :: CodeCorps.Organization.t | nil
defp get_organization(nil), do: nil
defp get_organization(id), do: CodeCorps.Repo.get(CodeCorps.Organization, id)
end