diff --git a/priv/repo/migrations/20170201035458_create_user_task.exs b/priv/repo/migrations/20170201035458_create_user_task.exs new file mode 100644 index 000000000..2ff9984b2 --- /dev/null +++ b/priv/repo/migrations/20170201035458_create_user_task.exs @@ -0,0 +1,16 @@ +defmodule CodeCorps.Repo.Migrations.CreateUserTask do + @moduledoc false + + use Ecto.Migration + + def change do + create table(:user_tasks) do + add :task_id, references(:tasks), null: false + add :user_id, references(:users), null: false + + timestamps() + end + + create index :user_tasks, [:user_id, :task_id], unique: true + end +end diff --git a/priv/repo/structure.sql b/priv/repo/structure.sql index 79a604de9..6efdfdc27 100644 --- a/priv/repo/structure.sql +++ b/priv/repo/structure.sql @@ -1062,6 +1062,38 @@ CREATE SEQUENCE task_lists_id_seq ALTER SEQUENCE task_lists_id_seq OWNED BY task_lists.id; +-- +-- Name: task_skills; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE task_skills ( + id integer NOT NULL, + skill_id integer NOT NULL, + task_id integer NOT NULL, + inserted_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL +); + + +-- +-- Name: task_skills_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE task_skills_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_skills_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE task_skills_id_seq OWNED BY task_skills.id; + + -- -- Name: tasks; Type: TABLE; Schema: public; Owner: - -- @@ -1199,6 +1231,38 @@ CREATE SEQUENCE user_skills_id_seq ALTER SEQUENCE user_skills_id_seq OWNED BY user_skills.id; +-- +-- Name: user_tasks; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE user_tasks ( + id integer NOT NULL, + task_id integer NOT NULL, + user_id integer NOT NULL, + inserted_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL +); + + +-- +-- Name: user_tasks_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE user_tasks_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user_tasks_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE user_tasks_id_seq OWNED BY user_tasks.id; + + -- -- Name: users; Type: TABLE; Schema: public; Owner: - -- @@ -1422,6 +1486,13 @@ ALTER TABLE ONLY stripe_platform_customers ALTER COLUMN id SET DEFAULT nextval(' ALTER TABLE ONLY task_lists ALTER COLUMN id SET DEFAULT nextval('task_lists_id_seq'::regclass); +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY task_skills ALTER COLUMN id SET DEFAULT nextval('task_skills_id_seq'::regclass); + + -- -- Name: id; Type: DEFAULT; Schema: public; Owner: - -- @@ -1450,6 +1521,13 @@ ALTER TABLE ONLY user_roles ALTER COLUMN id SET DEFAULT nextval('user_roles_id_s ALTER TABLE ONLY user_skills ALTER COLUMN id SET DEFAULT nextval('user_skills_id_seq'::regclass); +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY user_tasks ALTER COLUMN id SET DEFAULT nextval('user_tasks_id_seq'::regclass); + + -- -- Name: id; Type: DEFAULT; Schema: public; Owner: - -- @@ -1633,6 +1711,14 @@ ALTER TABLE ONLY task_lists ADD CONSTRAINT task_lists_pkey PRIMARY KEY (id); +-- +-- Name: task_skills_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY task_skills + ADD CONSTRAINT task_skills_pkey PRIMARY KEY (id); + + -- -- Name: user_categories_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -1657,6 +1743,14 @@ ALTER TABLE ONLY user_skills ADD CONSTRAINT user_skills_pkey PRIMARY KEY (id); +-- +-- Name: user_tasks_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY user_tasks + ADD CONSTRAINT user_tasks_pkey PRIMARY KEY (id); + + -- -- Name: users_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -1966,6 +2060,13 @@ CREATE UNIQUE INDEX stripe_platform_customers_user_id_index ON stripe_platform_c CREATE INDEX task_lists_project_id_index ON task_lists USING btree (project_id); +-- +-- Name: task_skills_task_id_skill_id_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX task_skills_task_id_skill_id_index ON task_skills USING btree (task_id, skill_id); + + -- -- Name: tasks_number_project_id_index; Type: INDEX; Schema: public; Owner: - -- @@ -2008,6 +2109,13 @@ CREATE UNIQUE INDEX user_categories_user_id_category_id_index ON user_categories CREATE UNIQUE INDEX user_roles_user_id_role_id_index ON user_roles USING btree (user_id, role_id); +-- +-- Name: user_tasks_user_id_task_id_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX user_tasks_user_id_task_id_index ON user_tasks USING btree (user_id, task_id); + + -- -- Name: users_email_index; Type: INDEX; Schema: public; Owner: - -- @@ -2301,6 +2409,22 @@ ALTER TABLE ONLY task_lists ADD CONSTRAINT task_lists_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id); +-- +-- Name: task_skills_skill_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY task_skills + ADD CONSTRAINT task_skills_skill_id_fkey FOREIGN KEY (skill_id) REFERENCES skills(id); + + +-- +-- Name: task_skills_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY task_skills + ADD CONSTRAINT task_skills_task_id_fkey FOREIGN KEY (task_id) REFERENCES tasks(id); + + -- -- Name: tasks_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -2373,9 +2497,25 @@ ALTER TABLE ONLY user_skills ADD CONSTRAINT user_skills_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id); +-- +-- Name: user_tasks_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY user_tasks + ADD CONSTRAINT user_tasks_task_id_fkey FOREIGN KEY (task_id) REFERENCES tasks(id); + + +-- +-- Name: user_tasks_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY user_tasks + ADD CONSTRAINT user_tasks_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id); + + -- -- PostgreSQL database dump complete -- -INSERT INTO "schema_migrations" (version) VALUES (20160723215749), (20160804000000), (20160804001111), (20160805132301), (20160805203929), (20160808143454), (20160809214736), (20160810124357), (20160815125009), (20160815143002), (20160816020347), (20160816034021), (20160817220118), (20160818000944), (20160818132546), (20160820113856), (20160820164905), (20160822002438), (20160822004056), (20160822011624), (20160822020401), (20160822044612), (20160830081224), (20160830224802), (20160911233738), (20160912002705), (20160912145957), (20160918003206), (20160928232404), (20161003185918), (20161019090945), (20161019110737), (20161020144622), (20161021131026), (20161031001615), (20161121005339), (20161121014050), (20161121043941), (20161121045709), (20161122015942), (20161123081114), (20161123150943), (20161124085742), (20161125200620), (20161126045705), (20161127054559), (20161205024856), (20161207112519), (20161209192504), (20161212005641), (20161214005935), (20161215052051), (20161216051447), (20161218005913), (20161219160401), (20161219163909), (20161220141753), (20161221085759), (20161226213600), (20161231063614), (20170102130055), (20170102181053), (20170104113708), (20170104212623), (20170104235423), (20170106013143), (20170115035159), (20170115230549), (20170121014100); +INSERT INTO "schema_migrations" (version) VALUES (20160723215749), (20160804000000), (20160804001111), (20160805132301), (20160805203929), (20160808143454), (20160809214736), (20160810124357), (20160815125009), (20160815143002), (20160816020347), (20160816034021), (20160817220118), (20160818000944), (20160818132546), (20160820113856), (20160820164905), (20160822002438), (20160822004056), (20160822011624), (20160822020401), (20160822044612), (20160830081224), (20160830224802), (20160911233738), (20160912002705), (20160912145957), (20160918003206), (20160928232404), (20161003185918), (20161019090945), (20161019110737), (20161020144622), (20161021131026), (20161031001615), (20161121005339), (20161121014050), (20161121043941), (20161121045709), (20161122015942), (20161123081114), (20161123150943), (20161124085742), (20161125200620), (20161126045705), (20161127054559), (20161205024856), (20161207112519), (20161209192504), (20161212005641), (20161214005935), (20161215052051), (20161216051447), (20161218005913), (20161219160401), (20161219163909), (20161220141753), (20161221085759), (20161226213600), (20161231063614), (20170102130055), (20170102181053), (20170104113708), (20170104212623), (20170104235423), (20170106013143), (20170115035159), (20170115230549), (20170121014100), (20170131234029), (20170201035458); diff --git a/test/controllers/user_task_controller_test.exs b/test/controllers/user_task_controller_test.exs new file mode 100644 index 000000000..5f6789732 --- /dev/null +++ b/test/controllers/user_task_controller_test.exs @@ -0,0 +1,101 @@ +defmodule CodeCorps.UserTaskControllerTest do + @moduledoc false + + use CodeCorps.ApiCase, resource_name: :user_task + + describe "index" do + test "lists all entries on index", %{conn: conn} do + [user_task_1, user_task_2] = insert_pair(:user_task) + + conn + |> request_index + |> json_response(200) + |> assert_ids_from_response([user_task_1.id, user_task_2.id]) + end + + test "filters resources on index", %{conn: conn} do + [user_task_1, user_task_2 | _] = insert_list(3, :user_task) + + path = "user-tasks/?filter[id]=#{user_task_1.id},#{user_task_2.id}" + + conn + |> get(path) + |> json_response(200) + |> assert_ids_from_response([user_task_1.id, user_task_2.id]) + end + end + + describe "show" do + test "shows chosen resource", %{conn: conn} do + user = insert(:user) + task = insert(:task) + user_task = insert(:user_task, task: task, user: user) + + conn + |> request_show(user_task) + |> json_response(200) + |> Map.get("data") + |> assert_result_id(user_task.id) + end + + test "renders 404 error when id is nonexistent", %{conn: conn} do + assert conn |> request_show(:not_found) |> json_response(404) + end + end + + describe "create" do + @tag :authenticated + test "creates and renders resource when data is valid", %{conn: conn, current_user: current_user} do + task = insert(:task, user: current_user) + user = insert(:user) + + attrs = %{task: task, user: user} + assert conn |> request_create(attrs) |> json_response(201) + end + + @tag :authenticated + test "renders 422 error when data is invalid", %{conn: conn, current_user: current_user} do + task = insert(:task, user: current_user) + + invalid_attrs = %{task: task, user: nil} + assert conn |> request_create(invalid_attrs) |> json_response(422) + end + + test "renders 401 when unauthenticated", %{conn: conn} do + assert conn |> request_create |> json_response(401) + end + + @tag :authenticated + test "renders 403 when not authorized", %{conn: conn} do + task = insert(:task) + user = insert(:user) + attrs = %{task: task, user: user} + + assert conn |> request_create(attrs) |> json_response(403) + end + end + + describe "delete" do + @tag :authenticated + test "deletes chosen resource", %{conn: conn, current_user: current_user} do + task = insert(:task, user: current_user) + user_task = insert(:user_task, task: task) + + assert conn |> request_delete(user_task) |> response(204) + end + + test "renders 401 when unauthenticated", %{conn: conn} do + assert conn |> request_delete |> json_response(401) + end + + @tag :authenticated + test "renders 403 when not authorized", %{conn: conn} do + assert conn |> request_delete |> json_response(403) + end + + @tag :authenticated + test "renders 404 when id is nonexistent on delete", %{conn: conn} do + assert conn |> request_delete(:not_found) |> json_response(404) + end + end +end diff --git a/test/models/user_task_test.exs b/test/models/user_task_test.exs new file mode 100644 index 000000000..a20cb5c26 --- /dev/null +++ b/test/models/user_task_test.exs @@ -0,0 +1,43 @@ +defmodule CodeCorps.UserTaskTest do + @moduledoc false + + use CodeCorps.ModelCase + + alias CodeCorps.UserTask + + describe "create_changeset/2" do + @required_attrs ~w(task_id user_id) + + test "requires #{@required_attrs}" do + changeset = UserTask.create_changeset(%UserTask{}, %{}) + + assert_validation_triggered(changeset, :task_id, :required) + assert_validation_triggered(changeset, :user_id, :required) + end + + test "ensures associated Task record exists" do + user = insert(:user) + changeset = UserTask.create_changeset(%UserTask{}, %{task_id: -1, user_id: user.id}) + + {:error, response_changeset} = Repo.insert(changeset) + assert_error_message(response_changeset, :task, "does not exist") + end + + test "ensures associated User record exists" do + task = insert(:task) + changeset = UserTask.create_changeset(%UserTask{}, %{task_id: task.id, user_id: -1}) + + {:error, response_changeset} = Repo.insert(changeset) + assert_error_message(response_changeset, :user, "does not exist") + end + + test "ensures uniqueness of User/Task combination" do + user_task = insert(:user_task) + + changeset = UserTask.create_changeset(%UserTask{}, %{task_id: user_task.task_id, user_id: user_task.user_id}) + + {:error, response_changeset} = Repo.insert(changeset) + assert_error_message(response_changeset, :user, "has already been taken") + end + end +end diff --git a/test/policies/user_task_policy_test.exs b/test/policies/user_task_policy_test.exs new file mode 100644 index 000000000..cadb0d57f --- /dev/null +++ b/test/policies/user_task_policy_test.exs @@ -0,0 +1,131 @@ +defmodule CodeCorps.UserTaskPolicyTest do + @moduledoc false + + use CodeCorps.PolicyCase + + import CodeCorps.UserTaskPolicy, only: [create?: 2, delete?: 2] + import CodeCorps.UserTask, only: [create_changeset: 2] + + alias CodeCorps.UserTask + + defp generate_data_for(role) do + {user, project} = insert_user_and_project(role) + + task = case role do + "author" -> insert(:task, project: project, user: user) + _ -> insert(:task, project: project) + end + + {user, task} + end + + defp insert_user_and_project(role) do + user = insert(:user) + organization = insert(:organization) + project = insert(:project, organization: organization) + + insert_membership(user, organization, role) + + {user, project} + end + + defp insert_membership(_, _, role) when role in ~w(non-member author), do: nil + defp insert_membership(user, organization, role) do + insert(:organization_membership, organization: organization, member: user, role: role) + end + + describe "create?" do + test "returns false when user is not member of organization" do + {user, task} = generate_data_for("non-member") + + changeset = %UserTask{} |> create_changeset(%{task_id: task.id}) + refute create?(user, changeset) + end + + test "returns false when user is pending member of organization" do + {user, task} = generate_data_for("pending") + + changeset = %UserTask{} |> create_changeset(%{task_id: task.id}) + refute create?(user, changeset) + end + + test "returns true when user is contributor of organization" do + {user, task} = generate_data_for("contributor") + + changeset = %UserTask{} |> create_changeset(%{task_id: task.id}) + assert create?(user, changeset) + end + + test "returns true when user is admin of organization" do + {user, task} = generate_data_for("admin") + + changeset = %UserTask{} |> create_changeset(%{task_id: task.id}) + assert create?(user, changeset) + end + + test "returns true when user is owner of organization" do + {user, task} = generate_data_for("owner") + + changeset = %UserTask{} |> create_changeset(%{task_id: task.id}) + assert create?(user, changeset) + end + + test "returns true when user is author of task" do + {user, task} = generate_data_for("author") + + changeset = %UserTask{} |> create_changeset(%{task_id: task.id}) + + assert create?(user, changeset) + end + end + + describe "delete?" do + test "returns false when user is not member of organization" do + {user, task} = generate_data_for("non-member") + + user_task = insert(:user_task, task: task) + + refute delete?(user, user_task) + end + + test "returns false when user is pending member of organization" do + {user, task} = generate_data_for("pending") + + user_task = insert(:user_task, task: task) + + refute delete?(user, user_task) + end + + test "returns true when user is contributor of organization" do + {user, task} = generate_data_for("contributor") + + user_task = insert(:user_task, task: task) + + assert delete?(user, user_task) + end + + test "returns true when user is admin of organization" do + {user, task} = generate_data_for("admin") + + user_task = insert(:user_task, task: task) + + assert delete?(user, user_task) + end + + test "returns true when user is owner of organization" do + {user, task} = generate_data_for("owner") + + user_task = insert(:user_task, task: task) + + assert delete?(user, user_task) + end + + test "returns true when user is author of task" do + {user, task} = generate_data_for("author") + + user_task = insert(:user_task, task: task) + + assert delete?(user, user_task) + end + end +end diff --git a/test/support/factories.ex b/test/support/factories.ex index df6a0dffc..02e58a842 100644 --- a/test/support/factories.ex +++ b/test/support/factories.ex @@ -241,6 +241,13 @@ defmodule CodeCorps.Factories do } end + def user_task_factory do + %CodeCorps.UserTask{ + user: build(:user), + task: build(:task) + } + end + def project_skill_factory do %CodeCorps.ProjectSkill{ project: build(:project), diff --git a/test/views/user_task_view_test.exs b/test/views/user_task_view_test.exs new file mode 100644 index 000000000..8fa34daee --- /dev/null +++ b/test/views/user_task_view_test.exs @@ -0,0 +1,32 @@ +defmodule CodeCorps.UserTaskViewTest do + @moduledoc false + + use CodeCorps.ViewCase + + test "renders all attributes and relationships properly" do + user_task = insert(:user_task) + + rendered_json = render(CodeCorps.UserTaskView, "show.json-api", data: user_task) + + expected_json = %{ + "data" => %{ + "id" => user_task.id |> Integer.to_string, + "type" => "user-task", + "attributes" => %{}, + "relationships" => %{ + "task" => %{ + "data" => %{"id" => user_task.task_id |> Integer.to_string, "type" => "task"} + }, + "user" => %{ + "data" => %{"id" => user_task.user_id |> Integer.to_string, "type" => "user"} + } + } + }, + "jsonapi" => %{ + "version" => "1.0" + } + } + + assert rendered_json == expected_json + end +end diff --git a/web/controllers/user_task_controller.ex b/web/controllers/user_task_controller.ex new file mode 100644 index 000000000..13b56a0f2 --- /dev/null +++ b/web/controllers/user_task_controller.ex @@ -0,0 +1,23 @@ +defmodule CodeCorps.UserTaskController do + use CodeCorps.Web, :controller + use JaResource + + import CodeCorps.Helpers.Query, only: [id_filter: 2] + + alias CodeCorps.UserTask + + plug :load_resource, model: UserTask, only: [:show], preload: [:task, :user] + plug :load_and_authorize_changeset, model: UserTask, only: [:create] + plug :load_and_authorize_resource, model: UserTask, only: [:delete] + plug JaResource + + @spec filter(Plug.Conn.t, Ecto.Query.t, String.t, list) :: Plug.Conn.t + def filter(_conn, query, "id", id_list) do + query |> id_filter(id_list) + end + + @spec handle_create(Plug.Conn.t, map) :: Ecto.Changeset.t + def handle_create(_conn, attributes) do + %UserTask{} |> UserTask.create_changeset(attributes) + end +end diff --git a/web/models/abilities.ex b/web/models/abilities.ex index f56cc1962..1c02a3312 100644 --- a/web/models/abilities.ex +++ b/web/models/abilities.ex @@ -21,6 +21,7 @@ defmodule Canary.Abilities do alias CodeCorps.UserCategory alias CodeCorps.UserRole alias CodeCorps.UserSkill + alias CodeCorps.UserTask alias CodeCorps.CategoryPolicy alias CodeCorps.CommentPolicy @@ -44,6 +45,7 @@ defmodule Canary.Abilities do alias CodeCorps.UserCategoryPolicy alias CodeCorps.UserRolePolicy alias CodeCorps.UserSkillPolicy + alias CodeCorps.UserTaskPolicy alias Ecto.Changeset @@ -122,5 +124,8 @@ defmodule Canary.Abilities do def can?(%User{} = user, :create, %Changeset{data: %UserSkill{}} = changeset), do: UserSkillPolicy.create?(user, changeset) def can?(%User{} = user, :delete, %UserSkill{} = user_skill), do: UserSkillPolicy.delete?(user, user_skill) + + def can?(%User{} = user, :create, %Changeset{data: %UserTask{}} = changeset), do: UserTaskPolicy.create?(user, changeset) + def can?(%User{} = user, :delete, %UserTask{} = user_task), do: UserTaskPolicy.delete?(user, user_task) end end diff --git a/web/models/user_task.ex b/web/models/user_task.ex new file mode 100644 index 000000000..987132ee4 --- /dev/null +++ b/web/models/user_task.ex @@ -0,0 +1,32 @@ +defmodule CodeCorps.UserTask do + @moduledoc """ + Represents a link record between a task and a user, indicating that + the task was assigned to the user. + """ + use CodeCorps.Web, :model + + @type t :: %__MODULE__{} + + schema "user_tasks" do + belongs_to :user, CodeCorps.Skill + belongs_to :task, CodeCorps.Task + + timestamps() + end + + @permitted_attrs [:user_id, :task_id] + @required_attrs @permitted_attrs + + @doc """ + Builds a changeset used to insert a record into the database + """ + @spec create_changeset(CodeCorps.UserTask.t, map) :: Ecto.Changeset.t + def create_changeset(struct, params \\ %{}) do + struct + |> cast(params, @permitted_attrs) + |> validate_required(@required_attrs) + |> assoc_constraint(:task) + |> assoc_constraint(:user) + |> unique_constraint(:user, name: :user_tasks_user_id_task_id_index) + end +end diff --git a/web/policies/user_task_policy.ex b/web/policies/user_task_policy.ex new file mode 100644 index 000000000..86131ef76 --- /dev/null +++ b/web/policies/user_task_policy.ex @@ -0,0 +1,34 @@ +defmodule CodeCorps.UserTaskPolicy do + @moduledoc """ + Represents an authorization policy for performing actions on UserTask records. + Used to authorize a controller action. + """ + import CodeCorps.Helpers.Policy, + only: [get_project: 1, get_membership: 2, get_role: 1, contributor_or_higher?: 1] + + alias CodeCorps.{Repo, Task, UserTask, User} + alias Ecto.Changeset + + @spec create?(User.t, Changeset.t) :: boolean + def create?(%User{} = user, %Changeset{} = changeset) do + cond do + changeset |> get_task |> get_project |> get_membership(user) |> get_role |> contributor_or_higher? -> true + changeset |> get_task |> authored_by?(user) -> true + true -> false + end + end + + @spec delete?(User.t, UserTask.t) :: boolean + def delete?(%User{} = user, %UserTask{} = user_task) do + cond do + user_task |> get_task |> get_project |> get_membership(user) |> get_role |> contributor_or_higher? -> true + user_task |> get_task |> authored_by?(user) -> true + true -> false + end + end + + defp get_task(%UserTask{task_id: task_id}), do: Repo.get(Task, task_id) + defp get_task(%Changeset{changes: %{task_id: task_id}}), do: Repo.get(Task, task_id) + + defp authored_by?(%Task{user_id: author_id}, %User{id: user_id}), do: user_id == author_id +end diff --git a/web/router.ex b/web/router.ex index f77f3609f..7e066bf68 100644 --- a/web/router.ex +++ b/web/router.ex @@ -78,6 +78,7 @@ defmodule CodeCorps.Router do resources "/user-categories", UserCategoryController, only: [:create, :delete] resources "/user-roles", UserRoleController, only: [:create, :delete] resources "/user-skills", UserSkillController, only: [:create, :delete] + resources "/user-tasks", UserTaskController, only: [:create, :delete] end scope "/", CodeCorps, host: "api." do @@ -110,6 +111,7 @@ defmodule CodeCorps.Router do resources "/user-categories", UserCategoryController, only: [:index, :show] resources "/user-roles", UserRoleController, only: [:index, :show] resources "/user-skills", UserSkillController, only: [:index, :show] + resources "/user-tasks", UserTaskController, only: [:index, :show] get "/:slug", SluggedRouteController, :show get "/:slug/projects", ProjectController, :index get "/:slug/:project_slug", ProjectController, :show diff --git a/web/views/user_task_view.ex b/web/views/user_task_view.ex new file mode 100644 index 000000000..628f979d6 --- /dev/null +++ b/web/views/user_task_view.ex @@ -0,0 +1,8 @@ +defmodule CodeCorps.UserTaskView do + use CodeCorps.PreloadHelpers, default_preloads: [:task, :user] + use CodeCorps.Web, :view + use JaSerializer.PhoenixView + + has_one :task, serializer: CodeCorps.TaskView + has_one :user, serializer: CodeCorps.UserView +end