Current section
Files
Jump to
Current section
Files
lib/user/UserServer.ex
defmodule User.UserServer do
require Logger
alias User.User
alias Utilities.Serialize
import Serialize
use GenServer, restart: :permanent
@storage "users.json"
def init(init_arg) do
{:ok, init_arg}
end
def start_link(_) do
state =
if File.exists?(@storage) do
retrieve_object(@storage)
|> Enum.map(fn
%User{} = user -> user
map when is_map(map) -> struct(User, map)
other -> other
end)
else
[]
end
IO.inspect(state)
GenServer.start_link(__MODULE__, state, name: :user_server)
end
def handle_call({:create_user, %User{} = user}, _caller_pid, state) do
username_taken = Enum.any?(state, &(&1.username == user.username))
cond do
username_taken ->
Logger.error("Username #{user.username} is alreay taken")
{:reply, nil, state}
String.length(user.password) < 8 ->
Logger.error("Password is to short")
{:reply, nil, state}
true ->
Logger.info("User #{user.username} created")
new_state = [user | state]
store_object(@storage, new_state)
{:reply, user, new_state}
end
end
def handle_call({:update_user, %User{} = merged_user}, _caller_pid, state) do
user_index = Enum.find_index(state, &(&1.username == merged_user.username))
new_state =
if user_index do
List.replace_at(state, user_index, merged_user)
else
[merged_user | state]
end
store_object(@storage, new_state)
{:reply, :ok, new_state}
end
def handle_call({:auth_user, username, password}, _caller_pid, state) do
user = Enum.find(state, &User.is_valid?(&1, username, password))
case user do
nil ->
{:reply, nil, state}
user ->
{:reply, user, state}
end
end
def handle_call({:delete_user, username}, _caller_pid, state) do
user_exists = Enum.any?(state, &(&1.username == username))
if user_exists do
Logger.info("User #{username} deleted")
new_state = Enum.filter(state, &(&1.username != username))
store_object(@storage, new_state)
{:reply, :ok, new_state}
else
Logger.error("User #{username} not found")
{:reply, {:error, :not_found}, state}
end
end
def handle_call({:add_topic, username, topic}, _caller_pid, state) do
user_index = Enum.find_index(state, &(&1.username == username))
if user_index do
user = Enum.at(state, user_index)
new_user = %User{user | topics: Enum.uniq([topic | user.topics])}
new_state = List.replace_at(state, user_index, new_user)
store_object(@storage, new_state)
Logger.info("Topic #{topic} added to user #{username}")
{:reply, {:ok, new_user}, new_state}
else
Logger.error("User #{username} not found")
{:reply, {:error, :not_found}, state}
end
end
def handle_call({:remove_topic, username, topic}, _caller_pid, state) do
user_index = Enum.find_index(state, &(&1.username == username))
if user_index do
user = Enum.at(state, user_index)
new_user = %User{user | topics: List.delete(user.topics, topic)}
new_state = List.replace_at(state, user_index, new_user)
store_object(@storage, new_state)
Logger.info("Topic #{topic} removed from user #{username}")
{:reply, {:ok, new_user}, new_state}
else
Logger.error("User #{username} not found")
{:reply, {:error, :not_found}, state}
end
end
def handle_call({:get_state}, _caller_pid, state) do
{:reply, state, state}
end
def handle_call({:sync}, _caller_pid, state) do
all_users = get_all_users(state)
new_state =
all_users
|> Enum.group_by(& &1.username)
|> Enum.map(fn {_username, users} ->
first = List.first(users)
merged_topics = users |> Enum.flat_map(& &1.topics) |> Enum.uniq()
%{first | topics: merged_topics}
end)
store_object(@storage, new_state)
{:reply, {:ok, new_state}, new_state}
end
def get_all_users(state) do
remote_users =
Node.list()
|> Enum.flat_map(fn node ->
remote_server_pid = :rpc.call(node, Process, :whereis, [:user_server])
if remote_server_pid do
:rpc.call(node, GenServer, :call, [remote_server_pid, {:get_state}])
else
[]
end
end)
(state ++ remote_users)
|> Enum.filter(& &1)
end
end