From 3d2d6d2e7ff71a0c8b4999dae46ea436a7a42ccc Mon Sep 17 00:00:00 2001 From: Aayush Kumar Sahu Date: Fri, 17 May 2024 21:04:47 +0530 Subject: [PATCH] notes: use utc timestamp in db and use indian time on application layer --- config/config.exs | 3 + lib/accumulator/notes.ex | 75 ++++++++----------- lib/accumulator/notes/note.ex | 2 +- lib/accumulator/notes/workspace.ex | 2 +- .../live/notes_live/notes_live.ex | 55 ++++++++------ mix.exs | 3 +- mix.lock | 2 + .../20240515175433_notes_timezone.exs | 27 +++++++ 8 files changed, 102 insertions(+), 67 deletions(-) create mode 100644 priv/repo/migrations/20240515175433_notes_timezone.exs diff --git a/config/config.exs b/config/config.exs index 1d1156f..4aa3cbc 100644 --- a/config/config.exs +++ b/config/config.exs @@ -7,6 +7,9 @@ # General application configuration import Config +# Timezone config +config :elixir, :time_zone_database, TimeZoneInfo.TimeZoneDatabase + # Configures the endpoint config :accumulator, AccumulatorWeb.Endpoint, url: [host: "localhost"], diff --git a/lib/accumulator/notes.ex b/lib/accumulator/notes.ex index 99d612c..3ed7b13 100644 --- a/lib/accumulator/notes.ex +++ b/lib/accumulator/notes.ex @@ -3,42 +3,35 @@ defmodule Accumulator.Notes do alias Accumulator.Repo import Ecto.Query + @timezone "Asia/Kolkata" + def get_note_by_id(id) do - Repo.get(Note, id) + Repo.get!(Note, id) end - def get_notes_grouped_and_ordered_by_date(workspace_id, ending_date) do - date_tuple = ending_date |> Date.add(1) |> Date.to_erl() - - ending_date_time = - NaiveDateTime.from_erl!({date_tuple, {0, 0, 0}}) |> NaiveDateTime.truncate(:second) - - starting_date_time = - NaiveDateTime.add(ending_date_time, -10, :day) |> NaiveDateTime.truncate(:second) + def get_notes_grouped_and_ordered_by_date(workspace_id, ending_datetime) do + ending_datetime = ending_datetime |> DateTime.add(1) + starting_datetime = DateTime.add(ending_datetime, -10, :day) # [ [date, [notes]], [date, [notes]] ] - # TODO: think about how to store date result = from(n in Note, - where: n.inserted_at >= ^starting_date_time, - where: n.inserted_at <= ^ending_date_time, + where: n.inserted_at >= ^starting_datetime, + where: n.inserted_at <= ^ending_datetime, where: n.workspace_id == ^workspace_id, order_by: [asc: n.id] ) |> Repo.all() |> group_and_sort_notes() - {result, NaiveDateTime.to_date(starting_date_time)} + {result, starting_datetime} end - def get_notes_grouped_and_ordered_till_date(workspace_id, date) do - date_tuple = date |> Date.add(1) |> Date.to_erl() - - date = - NaiveDateTime.from_erl!({date_tuple, {0, 0, 0}}) |> NaiveDateTime.truncate(:second) + def get_notes_grouped_and_ordered_till_date(workspace_id, datetime) do + datetime = datetime |> DateTime.add(1) from(n in Note, - where: n.inserted_at >= ^date, + where: n.inserted_at >= ^datetime, where: n.workspace_id == ^workspace_id, order_by: [asc: n.id] ) @@ -61,7 +54,7 @@ defmodule Accumulator.Notes do end def delete_note(id) do - Repo.get!(Note, id) + get_note_by_id(id) |> Repo.delete() end @@ -79,8 +72,9 @@ defmodule Accumulator.Notes do defp group_and_sort_notes(notes) do notes + |> Enum.map(&convert_timestamps_tz/1) |> Enum.group_by(fn %{inserted_at: inserted_at} -> - inserted_at |> NaiveDateTime.to_date() |> Date.to_string() + inserted_at |> DateTime.to_date() |> Date.to_string() end) |> Enum.map(fn {date, notes} -> [date, notes] end) |> Enum.sort_by(fn [date, _] -> date end) @@ -88,16 +82,12 @@ defmodule Accumulator.Notes do # Workspace stuff - # TODOs: - # Handling pagination in workspace through dates while getting notes in a workspace - def get_all_workspaces() do - Repo.all(Workspace) + Repo.all(Workspace) |> Enum.map(&convert_timestamps_tz/1) end def get_workspace_by_id(id) do - # Workspace or nil - Repo.get(Workspace, id) + Repo.get!(Workspace, id) end def create_new_workspace(params) do @@ -112,13 +102,7 @@ defmodule Accumulator.Notes do end def delete_workspace(id) do - case get_workspace_by_id(id) do - nil -> - nil - - workspace -> - Repo.delete(workspace) - end + get_workspace_by_id(id) |> Repo.delete() end def create_note(workspace_id, note_params) do @@ -134,14 +118,21 @@ defmodule Accumulator.Notes do |> Repo.update() end - def get_notes_in_workspace(workspace_id) do - from(n in Note, where: n.workspace_id == ^workspace_id, order_by: [asc: n.id]) - |> Repo.all() + defp convert_timestamps_tz(map) do + map + |> Map.update!(:inserted_at, fn utc_timestamp -> + DateTime.shift_zone!(utc_timestamp, @timezone) + end) + |> Map.update!(:updated_at, fn utc_timestamp -> + DateTime.shift_zone!(utc_timestamp, @timezone) + end) end - # def assign_default_workspace_to_every_note() do - # from(n in Note, where: is_nil(n.workspace_id)) - # |> Repo.all() - # |> Enum.map(fn n -> Note.changeset(n, %{workspace_id: 4}) |> Repo.update!() end) - # end + def get_utc_datetime_from_date(date \\ Date.utc_today()) do + date_tuple = date |> Date.to_erl() + + NaiveDateTime.from_erl!({date_tuple, {0, 0, 0}}) + |> NaiveDateTime.add(1, :day) + |> DateTime.from_naive!("Etc/UTC") + end end diff --git a/lib/accumulator/notes/note.ex b/lib/accumulator/notes/note.ex index c9589e3..adb458a 100644 --- a/lib/accumulator/notes/note.ex +++ b/lib/accumulator/notes/note.ex @@ -5,7 +5,7 @@ defmodule Accumulator.Notes.Note do schema "notes" do field(:text, :string) belongs_to(:workspace, Accumulator.Notes.Workspace) - timestamps() + timestamps(type: :utc_datetime) end def changeset(note, params \\ %{}) do diff --git a/lib/accumulator/notes/workspace.ex b/lib/accumulator/notes/workspace.ex index d91fdf4..755255a 100644 --- a/lib/accumulator/notes/workspace.ex +++ b/lib/accumulator/notes/workspace.ex @@ -5,7 +5,7 @@ defmodule Accumulator.Notes.Workspace do schema "workspaces" do field(:title, :string) has_many(:notes, Accumulator.Notes.Note) - timestamps() + timestamps(type: :utc_datetime) end def changeset(workspace, params \\ %{}) do diff --git a/lib/accumulator_web/live/notes_live/notes_live.ex b/lib/accumulator_web/live/notes_live/notes_live.ex index 35124d5..ba9194a 100644 --- a/lib/accumulator_web/live/notes_live/notes_live.ex +++ b/lib/accumulator_web/live/notes_live/notes_live.ex @@ -15,10 +15,11 @@ defmodule AccumulatorWeb.NotesLive do workspaces = Notes.get_all_workspaces() default_workspace = get_default_workspace(workspaces) - start_date = Date.utc_today() - {notes, pagination_date} = - Notes.get_notes_grouped_and_ordered_by_date(default_workspace.id, start_date) + Notes.get_notes_grouped_and_ordered_by_date( + default_workspace.id, + Notes.get_utc_datetime_from_date() + ) socket |> stream_configure(:notes, dom_id: &Enum.at(&1, 0)) @@ -68,7 +69,11 @@ defmodule AccumulatorWeb.NotesLive do socket = case Notes.insert(note_changeset) do {:ok, _note} -> - {notes, _} = Notes.get_notes_grouped_and_ordered_by_date(workspace_id, Date.utc_today()) + {notes, _} = + Notes.get_notes_grouped_and_ordered_by_date( + workspace_id, + Notes.get_utc_datetime_from_date() + ) socket |> assign(form: empty_form()) @@ -122,7 +127,10 @@ defmodule AccumulatorWeb.NotesLive do with {:ok, _} <- Notes.update_note(note_id, note_params), :ok <- update_note_workspace(note_id, new_workspace_id) do {notes, _} = - Notes.get_notes_grouped_and_ordered_by_date(workspace_id, Date.utc_today()) + Notes.get_notes_grouped_and_ordered_by_date( + workspace_id, + Notes.get_utc_datetime_from_date() + ) socket |> stream(:notes, notes, reset: true) @@ -166,7 +174,10 @@ defmodule AccumulatorWeb.NotesLive do socket |> stream(:notes, notes, reset: true) |> push_event("new-note-scroll", %{}) else {notes, pagination_date} = - Notes.get_notes_grouped_and_ordered_by_date(workspace_id, Date.utc_today()) + Notes.get_notes_grouped_and_ordered_by_date( + workspace_id, + Notes.get_utc_datetime_from_date() + ) socket |> stream(:notes, notes, reset: true) @@ -183,7 +194,10 @@ defmodule AccumulatorWeb.NotesLive do workspace = Notes.get_workspace_by_id(id) {notes, pagination_date} = - Notes.get_notes_grouped_and_ordered_by_date(workspace.id, Date.utc_today()) + Notes.get_notes_grouped_and_ordered_by_date( + workspace.id, + Notes.get_utc_datetime_from_date() + ) socket = socket @@ -210,22 +224,16 @@ defmodule AccumulatorWeb.NotesLive do end def handle_event("edit-workspace", %{"id" => id} = _params, socket) do - workspace = Notes.get_workspace_by_id(id) + workspace_form = Notes.get_workspace_by_id(id) |> Workspace.changeset() |> to_form() socket = - if workspace != nil do - workspace_form = workspace |> Workspace.changeset() |> to_form() - - socket - |> assign(workspace_edit_id: id) - |> assign(workspace_form: workspace_form) - |> push_event("notes-workspace-modal", %{ - modal_id: "workspace-modal", - attr: "data-show-modal" - }) - else - socket - end + socket + |> assign(workspace_edit_id: id) + |> assign(workspace_form: workspace_form) + |> push_event("notes-workspace-modal", %{ + modal_id: "workspace-modal", + attr: "data-show-modal" + }) {:noreply, socket} end @@ -306,7 +314,10 @@ defmodule AccumulatorWeb.NotesLive do workspace = get_default_workspace(socket.assigns.workspaces) {notes, pagination_date} = - Notes.get_notes_grouped_and_ordered_by_date(workspace.id, Date.utc_today()) + Notes.get_notes_grouped_and_ordered_by_date( + workspace.id, + Notes.get_utc_datetime_from_date() + ) socket = socket diff --git a/mix.exs b/mix.exs index f25ac8a..d372c94 100644 --- a/mix.exs +++ b/mix.exs @@ -58,7 +58,8 @@ defmodule Accumulator.MixProject do {:resend, "~> 0.4.1"}, {:ecto_psql_extras, "~> 0.6"}, {:libcluster, "~> 3.3"}, - {:earmark, "~> 1.4"} + {:earmark, "~> 1.4"}, + {:time_zone_info, "~> 0.7"} ] end diff --git a/mix.lock b/mix.lock index d70b3a7..aa311e2 100644 --- a/mix.lock +++ b/mix.lock @@ -31,6 +31,7 @@ "mint": {:hex, :mint, "1.6.0", "88a4f91cd690508a04ff1c3e28952f322528934be541844d54e0ceb765f01d5e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "3c5ae85d90a5aca0a49c0d8b67360bbe407f3b54f1030a111047ff988e8fefaa"}, "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"}, @@ -56,6 +57,7 @@ "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, "tesla": {:hex, :tesla, "1.9.0", "8c22db6a826e56a087eeb8cdef56889731287f53feeb3f361dec5d4c8efb6f14", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "7c240c67e855f7e63e795bf16d6b3f5115a81d1f44b7fe4eadbf656bae0fef8a"}, + "time_zone_info": {:hex, :time_zone_info, "0.7.3", "ab0ca1c249aef55794b3a71a9ba7184121f99436410f35a2525f67035c86c899", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7a549c7db4c7c1bd0fcac7dc8417c0b52a619950b0f495f4fb7c69a5302d1368"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"}, diff --git a/priv/repo/migrations/20240515175433_notes_timezone.exs b/priv/repo/migrations/20240515175433_notes_timezone.exs new file mode 100644 index 0000000..df36e07 --- /dev/null +++ b/priv/repo/migrations/20240515175433_notes_timezone.exs @@ -0,0 +1,27 @@ +defmodule Accumulator.Repo.Migrations.NotesTimezone do + use Ecto.Migration + + def change do + alter table(:workspaces) do + modify(:inserted_at, :utc_datetime) + modify(:updated_at, :utc_datetime) + end + + alter table(:notes) do + modify(:inserted_at, :utc_datetime) + modify(:updated_at, :utc_datetime) + end + end + + def down() do + alter table(:workspaces) do + modify(:inserted_at, :naive_datetime) + modify(:updated_at, :naive_datetime) + end + + alter table(:notes) do + modify(:inserted_at, :naive_datetime) + modify(:updated_at, :naive_datetime) + end + end +end