diff --git a/.gitignore b/.gitignore index f62cb5125fe65c55e7123f52285e8409f235928b..f5446b342143fb4b32b0a12a1ac4e602104e6ad0 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ diskuy-*.tar # this depending on your deployment strategy. /priv/static/ +.DS_store \ No newline at end of file diff --git a/config/dev.exs b/config/dev.exs index b56ce179f9c58dacdfea5c04b8b79a74004322ff..0350ba591471fdb4000a451bb54a892814eb0932 100755 --- a/config/dev.exs +++ b/config/dev.exs @@ -2,10 +2,11 @@ use Mix.Config # Configure your database config :diskuy, Diskuy.Repo, - username: "rafif", - password: "rafif123", - database: "diskuy_dev_new", + username: "postgres", + password: "postgres", + database: "diskuy", hostname: "localhost", + port: 5432, show_sensitive_data_on_connection_error: true, pool_size: 10 diff --git a/entrypoint.sh b/entrypoint.sh index 71d524fd774dec8c4ee440bd62e2a8304821a54f..eff59404000540d91e551e70853066790e7e2033 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -3,10 +3,10 @@ # docker entrypoint script. # assign a default for the database_user -DB_USER=${DATABASE_USER:-postgres} +DB_USER=${PGUSER:-postgres} # wait until Postgres is ready -while ! pg_isready -q -h $DATABASE_HOST -p 5432 -U $DB_USER +while ! pg_isready -q -h $PGHOST -p $PGPORT -U $DB_USER do echo "$(date) - waiting for database to start" sleep 2 diff --git a/lib/diskuy/account.ex b/lib/diskuy/account.ex index b9557a3af9102c73aaaba775160ae71a051ba9aa..b27a9dbc1ca6e56959806bcaef4ef5ae82e8a69b 100644 --- a/lib/diskuy/account.ex +++ b/lib/diskuy/account.ex @@ -7,6 +7,9 @@ defmodule Diskuy.Account do alias Diskuy.Repo alias Diskuy.Account.User + alias Diskuy.Forum.Post + alias Diskuy.Forum.Thread + alias Diskuy.Forum.Topic @doc """ Returns the list of users. @@ -41,6 +44,7 @@ defmodule Diskuy.Account do case Repo.get_by(User, email: email) do nil -> {:error, :not_found} + user -> {:ok, user} end @@ -52,6 +56,7 @@ defmodule Diskuy.Account do case Repo.get_by(User, username: username) do nil -> {:error, :not_found} + user -> {:ok, user} end @@ -123,4 +128,65 @@ defmodule Diskuy.Account do def change_user(%User{} = user, attrs \\ %{}) do User.changeset(user, attrs) end + + def get_points_by_user_id(id) do + points_threads_query = + from t in Thread, + select: %{total_points: sum(t.points)}, + where: t.user_id == ^id + + points_posts_query = + from p in Post, + select: %{total_points: sum(p.points)}, + where: p.user_id == ^id, + union: ^points_threads_query + + points_query = + from p in subquery(points_posts_query), + select: %{total_points: sum(p.total_points)} + + Repo.one(points_query) + end + + def get_badged_post_count(id, minimum_points) do + query = + from t in Thread, + limit: 1, + select: %{count: count(t.id) |> coalesce(0)}, + where: t.user_id == ^id and t.points >= ^minimum_points + + Repo.one(query) + end + + defp query_based_on_tresholds(query, tresholds) do + min_points = tresholds |> elem(0) + max_points = tresholds |> elem(1) + + if max_points != Nil do + where(query, [tr, u, to], tr.points >= ^min_points and tr.points < ^max_points) + else + where(query, [tr, u, to], tr.points >= ^min_points) + end + end + + def get_badged_posts(id, tresholds) do + Thread + |> join(:inner, [tr], u in User, as: :users, on: tr.user_id == u.id) + |> join(:inner, [tr], to in Topic, as: :topics, on: tr.topic_id == to.id) + |> where([tr, u, to], tr.user_id == ^id) + |> query_based_on_tresholds(tresholds) + |> select([tr, u, to], %{ + id: tr.id, + title: tr.title, + content: tr.content, + points: tr.points, + user_id: tr.user_id, + username: u.username, + topic_id: tr.topic_id, + topic_name: to.name, + inserted_at: tr.inserted_at, + updated_at: tr.updated_at + }) + |> Repo.all() + end end diff --git a/lib/diskuy/forum.ex b/lib/diskuy/forum.ex index 5944ca7e202edd1f288c0577fca8a6f5ba7cdbe4..8f3ceb61681764f376755e016a4ace26d3d85920 100644 --- a/lib/diskuy/forum.ex +++ b/lib/diskuy/forum.ex @@ -298,4 +298,5 @@ defmodule Diskuy.Forum do def change_post(%Post{} = post, attrs \\ %{}) do Post.changeset(post, attrs) end + end diff --git a/lib/diskuy/forum_post_page.ex b/lib/diskuy/forum_post_page.ex index 421ec5d00024344ebf5e6f06f99127026c14be11..b154ec4b5c87f4c18ea5586e117b23571ddba4d5 100644 --- a/lib/diskuy/forum_post_page.ex +++ b/lib/diskuy/forum_post_page.ex @@ -14,6 +14,12 @@ defmodule Diskuy.ForumPostPage do |> order_by([p], asc: p.inserted_at) end + def post_per_user(username) do + query_start() + |> where([p, u], u.username == ^username) + |> order_by([p], desc: p.points, desc: p.inserted_at) + end + defp query_start do Post |> join(:inner, [p], u in User, as: :users, on: p.user_id == u.id) diff --git a/lib/diskuy/leaderboard.ex b/lib/diskuy/leaderboard.ex new file mode 100644 index 0000000000000000000000000000000000000000..43b22a5da55461e5311c5133375e46416cf7130b --- /dev/null +++ b/lib/diskuy/leaderboard.ex @@ -0,0 +1,34 @@ +defmodule Diskuy.Leaderboard do + + @moduledoc """ + The Query Builder For calculating the Leaderboard. + """ + + import Ecto.Query, warn: false + alias Diskuy.Repo + alias Diskuy.Forum.Thread + alias Diskuy.Forum.Post + alias Diskuy.Account.User + + def get_leaderboard do + in_query_1 = from t in Thread, + select: %{sum: sum(t.points), user_id: t.user_id}, + group_by: t.user_id + + in_query_2 = from p in Post, + select: %{sum: sum(p.points), user_id: p.user_id}, + group_by: p.user_id, + union_all: ^in_query_1 + + query = from res in subquery(in_query_2), + left_join: u in User, + on: res.user_id == u.id, + group_by: u.username, + select: %{points: sum(res.sum), user_name: u.username}, + limit: 10, + order_by: [desc: sum(res.sum) |> coalesce(0)] + + Repo.all(query) + end + +end diff --git a/lib/diskuy/likes/post_like.ex b/lib/diskuy/likes/post_like.ex index ce7ffef64c699b4c93b52254aa02ca402326a26c..b98a84de4536e18a4e2a8ff255cdf6b7ff4f7b12 100644 --- a/lib/diskuy/likes/post_like.ex +++ b/lib/diskuy/likes/post_like.ex @@ -5,6 +5,7 @@ defmodule Diskuy.Likes.PostLike do schema "post_likes" do field :user_id, :id field :post_id, :id + field :is_dislike, :boolean timestamps() end diff --git a/lib/diskuy/likes/thread_like.ex b/lib/diskuy/likes/thread_like.ex index e1627f818544d45971842902df61102239342a29..4329d329d21ca98083a1d1160d52a28d90b0ff7e 100644 --- a/lib/diskuy/likes/thread_like.ex +++ b/lib/diskuy/likes/thread_like.ex @@ -5,6 +5,7 @@ defmodule Diskuy.Likes.ThreadLike do schema "thread_likes" do field :user_id, :id field :thread_id, :id + field :is_dislike, :boolean timestamps() end diff --git a/lib/diskuy_web/controllers/leaderboard_controller.ex b/lib/diskuy_web/controllers/leaderboard_controller.ex new file mode 100644 index 0000000000000000000000000000000000000000..b77477f18d9a9ea0cfa2e7d8cf807e8de25d7015 --- /dev/null +++ b/lib/diskuy_web/controllers/leaderboard_controller.ex @@ -0,0 +1,12 @@ +defmodule DiskuyWeb.LeaderboardController do + use DiskuyWeb, :controller + alias Diskuy.Leaderboard + + action_fallback DiskuyWeb.FallbackController + + def index(conn, _params) do + leaderboard = Leaderboard.get_leaderboard() + render(conn, "index.json", leaderboard: leaderboard) + end + +end diff --git a/lib/diskuy_web/controllers/post_controller.ex b/lib/diskuy_web/controllers/post_controller.ex index 76bc66361713c778e457967248e4fad5666ff826..1d98f29250196d513838350bb8b14c9c50d891da 100644 --- a/lib/diskuy_web/controllers/post_controller.ex +++ b/lib/diskuy_web/controllers/post_controller.ex @@ -55,7 +55,7 @@ defmodule DiskuyWeb.PostController do with {:ok, %PostLike{}} <- Likes.create_post_like(%{"user_id" => current_user.id, "post_id" => id}), - {:ok, %Post{} = post} <- Forum.update_post(post, %{"points" => (post.points+1)}) do + {:ok, %Post{} = post} <- Forum.update_post(post, %{"points" => (post.points + 1)}) do render(conn, "show.json", post: post) end end @@ -66,7 +66,30 @@ defmodule DiskuyWeb.PostController do post_like = Likes.get_post_like_by_refer!(current_user.id, id) with {:ok, %PostLike{}} <- Likes.delete_post_like(post_like), - {:ok, %Post{}} <- Forum.update_post(post, %{"points" => (post.points-1)}) do + {:ok, %Post{}} <- Forum.update_post(post, %{"points" => (post.points - 1)}) do + render(conn, "show.json", post: post) + end + end + + def add_dislike(conn, %{"id" => id}) do + current_user = Guardian.Plug.current_resource(conn) + post = Forum.get_post!(id) + + with {:ok, %PostLike{}} <- Likes.create_post_like(%{"user_id" => current_user.id, + "post_id" => id, + "is_dislike" => true}), + {:ok, %Post{} = post} <- Forum.update_post(post, %{"points" => (post.points - 1)}) do + render(conn, "show.json", post: post) + end + end + + def delete_dislike(conn, %{"id" => id}) do + current_user = Guardian.Plug.current_resource(conn) + post = Forum.get_post!(id) + post_like = Likes.get_post_like_by_refer!(current_user.id, id) + + with {:ok, %PostLike{}} <- Likes.delete_post_like(post_like), + {:ok, %Post{}} <- Forum.update_post(post, %{"points" => (post.points + 1)}) do render(conn, "show.json", post: post) end end diff --git a/lib/diskuy_web/controllers/post_pages_controller.ex b/lib/diskuy_web/controllers/post_pages_controller.ex index 9e9571957859b3739d6ab9f9ce0d8c6764f3dfce..b70ddfb02e5b550f52ccec9e3a761fbcb73d3629 100644 --- a/lib/diskuy_web/controllers/post_pages_controller.ex +++ b/lib/diskuy_web/controllers/post_pages_controller.ex @@ -10,6 +10,11 @@ defmodule DiskuyWeb.PostPagesController do paginate_and_render(conn, query) end + def pages_user(conn, %{"username" => username}) do + query = ForumPostPage.post_per_user(username) + paginate_and_render(conn, query) + end + defp paginate_and_render(conn, query) do case conn.params do %{"page" => page_num} -> diff --git a/lib/diskuy_web/controllers/thread_controller.ex b/lib/diskuy_web/controllers/thread_controller.ex index 9ba486fd3caa82f6f0f3e88a8e2d646b9cbdb089..a246488dd58bfb2099b7cb810d7c1055c19897d3 100644 --- a/lib/diskuy_web/controllers/thread_controller.ex +++ b/lib/diskuy_web/controllers/thread_controller.ex @@ -54,7 +54,7 @@ defmodule DiskuyWeb.ThreadController do with {:ok, %ThreadLike{} = _thread_like} <- Likes.create_thread_like(%{"user_id" => current_user.id, "thread_id" => id}), - {:ok, %Thread{} = thread} <- Forum.update_thread(thread, %{"points" => (thread.points+ 1)}) do + {:ok, %Thread{} = thread} <- Forum.update_thread(thread, %{"points" => (thread.points + 1)}) do render(conn, "show.json", thread: thread) end end @@ -65,7 +65,30 @@ defmodule DiskuyWeb.ThreadController do thread_like = Likes.get_thread_like_by_refer!(current_user.id, id) with {:ok, %ThreadLike{}} <- Likes.delete_thread_like(thread_like), - {:ok, %Thread{} = thread} <- Forum.update_thread(thread, %{"points" => (thread.points-1)}) do + {:ok, %Thread{} = thread} <- Forum.update_thread(thread, %{"points" => (thread.points - 1)}) do + render(conn, "show.json", thread: thread) + end + end + + def add_dislike(conn, %{"id" => id}) do + current_user = Guardian.Plug.current_resource(conn) + thread = Forum.get_thread!(id) + + with {:ok, %ThreadLike{} = _thread_like} <- Likes.create_thread_like(%{"user_id" => current_user.id, + "thread_id" => id, + "is_dislike" => true}), + {:ok, %Thread{} = thread} <- Forum.update_thread(thread, %{"points" => (thread.points - 1)}) do + render(conn, "show.json", thread: thread) + end + end + + def delete_dislike(conn, %{"id" => id}) do + current_user = Guardian.Plug.current_resource(conn) + thread = Forum.get_thread!(id) + thread_like = Likes.get_thread_like_by_refer!(current_user.id, id) + + with {:ok, %ThreadLike{}} <- Likes.delete_thread_like(thread_like), + {:ok, %Thread{} = thread} <- Forum.update_thread(thread, %{"points" => (thread.points + 1)}) do render(conn, "show.json", thread: thread) end end diff --git a/lib/diskuy_web/controllers/user_controller.ex b/lib/diskuy_web/controllers/user_controller.ex index 4d73833b6528b2adb89b78f4cf01c2f267b89f7d..c79d5a1f79fa961edaa5ac0a58ce23fb0acdbef0 100644 --- a/lib/diskuy_web/controllers/user_controller.ex +++ b/lib/diskuy_web/controllers/user_controller.ex @@ -6,6 +6,10 @@ defmodule DiskuyWeb.UserController do alias DiskuyWeb.Auth.Guardian alias DiskuyWeb.Auth.GoogleAuth + @bronze_treshold 5 + @silver_treshold 20 + @gold_treshold 50 + action_fallback DiskuyWeb.FallbackController def index(conn, _params) do @@ -33,10 +37,48 @@ defmodule DiskuyWeb.UserController do render(conn, "show.json", user: user) end + def get_user_points(conn, %{"name" => name}) do + user = Account.get_by_username!(name) + points = Account.get_points_by_user_id(user.id) + render(conn, "show_user_points.json", points: points.total_points) + end + + def get_user_badges(conn, %{"name" => name}) do + user = Account.get_by_username!(name) + + gold_badges = Account.get_badged_post_count(user.id, @gold_treshold) + silver_badges = Account.get_badged_post_count(user.id, @silver_treshold) + bronze_badges = Account.get_badged_post_count(user.id, @bronze_treshold) + + render(conn, "show_user_badges.json", + badges: %{ + gold: gold_badges.count, + silver: silver_badges.count - gold_badges.count, + bronze: bronze_badges.count - silver_badges.count + } + ) + end + + def get_user_badged_posts(conn, %{"name" => name, "badge" => badge}) do + user = Account.get_by_username!(name) + + badge_name_treshold_map = %{ + "bronze" => {@bronze_treshold, @silver_treshold}, + "silver" => {@silver_treshold, @gold_treshold}, + "gold" => {@gold_treshold, Nil} + } + + badge_treshold = badge_name_treshold_map[badge] + + badged_posts = Account.get_badged_posts(user.id, badge_treshold) + + render(conn, "show_user_badged_posts.json", posts: badged_posts) + end def update(conn, %{"user" => user_params}) do user = Guardian.Plug.current_resource(conn) new_user_params = user_params |> Map.drop(["email", "id", "role"]) + with {:ok, %User{} = user} <- Account.update_user(user, new_user_params) do render(conn, "show.json", user: user) end @@ -65,19 +107,22 @@ defmodule DiskuyWeb.UserController do def callback(conn, _params) do token_header = get_req_header(conn, "authorization") + case token_header do [] -> {:error, :unauthorized} - [token_bearer]-> - ["Bearer", token|_] = String.split(token_bearer, " ") - with {:ok, user, new_token}= GoogleAuth.create_local_token(token) do + + [token_bearer] -> + ["Bearer", token | _] = String.split(token_bearer, " ") + + with {:ok, user, new_token} = GoogleAuth.create_local_token(token) do conn |> put_status(:created) |> render("user_token.json", %{user: user, token: new_token}) end + _ -> {:error, :bad_request} end end - end diff --git a/lib/diskuy_web/router.ex b/lib/diskuy_web/router.ex index 269ac2c1b3a3f29f62b45e892d50a1a6358ab6b9..e18bb2a67c6be18e0647d48641d3c7b9a76adcbe 100644 --- a/lib/diskuy_web/router.ex +++ b/lib/diskuy_web/router.ex @@ -28,18 +28,29 @@ defmodule DiskuyWeb.Router do post "/threads/like/:id", ThreadController, :add_like options "/threads/like/:id", ThreadController, :options - post "/threads/dislike/:id", ThreadController, :delete_like + post "/threads/unlike/:id", ThreadController, :delete_like + options "/threads/unlike/:id", ThreadController, :options + + post "/threads/dislike/:id", ThreadController, :add_dislike options "/threads/dislike/:id", ThreadController, :options + post "/threads/undislike/:id", ThreadController, :delete_dislike + options "/threads/undislike/:id", ThreadController, :options + get "/threads/checklike/:id", ThreadLikeController, :check_like options "/threads/checklike/:id", ThreadLikeController, :options post "/post/like/:id", PostController, :add_like options "/post/like/:id", PostController, :options - post "/post/dislike/:id", PostController, :delete_like - options "/post/dislike/:id", PostController, :options + post "/post/unlike/:id", PostController, :delete_like + options "/post/unlike/:id", PostController, :options get "/post/checklike/:id", PostLikeController, :check_like options "/post/checklike/:id", PostLikeController, :options + post "/post/dislike/:id", PostController, :add_dislike + options "/post/dislike/:id", PostController, :options + post "/post/undislike/:id", PostController, :delete_dislike + options "/post/undislike/:id", PostController, :options + end scope "/api", DiskuyWeb do @@ -49,6 +60,9 @@ defmodule DiskuyWeb.Router do options "/users", UserController, :options options "/users/:id", UserController, :options get "/users/name/:name", UserController, :show_by_username + get "/users/points/:name", UserController, :get_user_points + get "/users/badges/:name", UserController, :get_user_badges + get "/users/:name/badges/:badge", UserController, :get_user_badged_posts options "/users/name/:name", UserController, :options resources "/topics", TopicController, except: [:new, :edit, :create, :update, :delete] @@ -73,12 +87,15 @@ defmodule DiskuyWeb.Router do get "/post/pages/thread/:thread_id", PostPagesController, :pages_thread + get "/post/pages/user/:username", PostPagesController, :pages_user options "/post/pages/thread/:thread_id", PostPagesController, :options resources "/post", PostController, except: [:new, :edit, :create, :update, :delete] options "/post", PostController, :options options "/post/:id", PostController, :options + resources "/leaderboard", LeaderboardController + # post "/users/signup", UserController, :create # options "/users/signup", PostController, :options # post "/users/signin", UserController, :signin diff --git a/lib/diskuy_web/views/leaderboard_view.ex b/lib/diskuy_web/views/leaderboard_view.ex new file mode 100644 index 0000000000000000000000000000000000000000..8669f5f930d41a30c01b76d1e95ce4c2da73c1c9 --- /dev/null +++ b/lib/diskuy_web/views/leaderboard_view.ex @@ -0,0 +1,8 @@ +defmodule DiskuyWeb.LeaderboardView do + use DiskuyWeb, :view + + def render("index.json", %{leaderboard: leaderboard}) do + %{data: leaderboard} + end + +end diff --git a/lib/diskuy_web/views/post_like_view.ex b/lib/diskuy_web/views/post_like_view.ex index c50e057dcb2d659abff1790fb113c3d14d382343..67b620e232ed1ae7119546d7510960da43a36b0f 100644 --- a/lib/diskuy_web/views/post_like_view.ex +++ b/lib/diskuy_web/views/post_like_view.ex @@ -13,6 +13,7 @@ defmodule DiskuyWeb.PostLikeView do def render("post_like.json", %{post_like: post_like}) do %{id: post_like.id, user_id: post_like.user_id, - thread_id: post_like.post_id} + thread_id: post_like.post_id, + is_dislike: post_like.is_dislike} end end diff --git a/lib/diskuy_web/views/thread_like_view.ex b/lib/diskuy_web/views/thread_like_view.ex index 7b4830199442783cf8673ef7bed445091b969285..f35ae0e79828c3820fe690d42494ea600d788bc6 100644 --- a/lib/diskuy_web/views/thread_like_view.ex +++ b/lib/diskuy_web/views/thread_like_view.ex @@ -13,6 +13,8 @@ defmodule DiskuyWeb.ThreadLikeView do def render("thread_like.json", %{thread_like: thread_like}) do %{id: thread_like.id, user_id: thread_like.user_id, - thread_id: thread_like.thread_id} + thread_id: thread_like.thread_id, + is_dislike: thread_like.is_dislike + } end end diff --git a/lib/diskuy_web/views/user_view.ex b/lib/diskuy_web/views/user_view.ex index a2b27322191518c5c9730c169cc10aba4cbca436..28f8a22d934ac7e9394e3451f23014a3cda7ee9d 100644 --- a/lib/diskuy_web/views/user_view.ex +++ b/lib/diskuy_web/views/user_view.ex @@ -14,13 +14,26 @@ defmodule DiskuyWeb.UserView do %{data: render_one(user, UserView, "user_token.json")} end + def render("show_user_points.json", %{points: points}) do + %{data: render_one(points, UserView, "user_points.json")} + end + + def render("show_user_badges.json", %{badges: badges}) do + %{data: render_one(badges, UserView, "user_badges.json")} + end + + def render("show_user_badged_posts.json", %{posts: posts}) do + %{data: render_many(posts, UserView, "user_badged_post.json", as: :post)} + end + def render("user.json", %{user: user}) do - %{id: user.id, + %{ + id: user.id, username: user.username, name: user.email, picture: user.picture, role: user.role - } + } end def render("user_token.json", %{user: user, token: token}) do @@ -33,4 +46,33 @@ defmodule DiskuyWeb.UserView do token: token } end + + def render("user_points.json", %{user: points}) do + %{ + points: points + } + end + + def render("user_badges.json", %{user: badges}) do + %{ + gold: badges.gold, + silver: badges.silver, + bronze: badges.bronze + } + end + + def render("user_badged_post.json", %{post: post}) do + %{ + id: post.id, + title: post.title, + points: post.points, + content: post.content, + user_id: post.user_id, + username: post.username, + topic_id: post.topic_id, + topic_name: post.topic_name, + inserted_at: post.inserted_at, + updated_at: post.updated_at + } + end end diff --git a/priv/repo/migrations/20211030064247_alter_thread_like_add_is_dislike.exs b/priv/repo/migrations/20211030064247_alter_thread_like_add_is_dislike.exs new file mode 100644 index 0000000000000000000000000000000000000000..c18de0b9366e1b0281ccdff66c085fdd04e32555 --- /dev/null +++ b/priv/repo/migrations/20211030064247_alter_thread_like_add_is_dislike.exs @@ -0,0 +1,9 @@ +defmodule Diskuy.Repo.Migrations.AlterThreadLikeAddIsDislike do + use Ecto.Migration + + def change do + alter table(:thread_likes) do + add :is_dislike, :boolean, default: false + end + end +end diff --git a/priv/repo/migrations/20211127160455_alter_post_like_add_is_dislike.exs b/priv/repo/migrations/20211127160455_alter_post_like_add_is_dislike.exs new file mode 100644 index 0000000000000000000000000000000000000000..97a9688d8c98b964f4d6206d208883137df14818 --- /dev/null +++ b/priv/repo/migrations/20211127160455_alter_post_like_add_is_dislike.exs @@ -0,0 +1,9 @@ +defmodule Diskuy.Repo.Migrations.AlterPostLikeAddIsDislike do + use Ecto.Migration + + def change do + alter table(:post_likes) do + add :is_dislike, :boolean, default: false + end + end +end