diff --git a/.formatter.exs b/.formatter.exs index 54bd0b6..662c239 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,5 +1,6 @@ # Used by "mix format" [ import_deps: [:plug], + subdirectories: ["priv/repo/migrations"], inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] ] diff --git a/.gitignore b/.gitignore index 1a7dddd..e9412a3 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ jenot-*.tar # Temporary files, for example, from tests. /tmp/ + +/priv/db_* diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..c2565c2 --- /dev/null +++ b/config/config.exs @@ -0,0 +1,6 @@ +import Config + +config :jenot, + ecto_repos: [Jenot.Repo] + +import_config "#{config_env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index 3847710..a4fdd3f 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -1,4 +1,8 @@ +import Config + config :plug_live_reload, patterns: [ ~r"priv/static/.*(html|js|css|png|jpeg|jpg|gif|svg)$" ] + +config :jenot, Jenot.Repo, database: "priv/db_dev.sqlite" diff --git a/config/prod.exs b/config/prod.exs new file mode 100644 index 0000000..becde76 --- /dev/null +++ b/config/prod.exs @@ -0,0 +1 @@ +import Config diff --git a/config/runtime.exs b/config/runtime.exs new file mode 100644 index 0000000..ed2a275 --- /dev/null +++ b/config/runtime.exs @@ -0,0 +1,12 @@ +import Config + +config :web_push_elixir, + vapid_public_key: System.fetch_env!("VAPID_PUBLIC_KEY"), + vapid_private_key: System.fetch_env!("VAPID_PRIVATE_KEY"), + vapid_subject: System.fetch_env!("VAPID_SUBJECT") + +config :jenot, Jenot.Repo, key: System.fetch_env!("EXQLITE_SECRET") + +if config_env() == :prod do + config :jenot, Jenot.Repo, database: System.get_env("EXQLITE_PATH") +end diff --git a/config/test.exs b/config/test.exs new file mode 100644 index 0000000..1e38a63 --- /dev/null +++ b/config/test.exs @@ -0,0 +1,3 @@ +import Config + +config :jenot, Jenot.Repo, database: :memory diff --git a/lib/jenot/account.ex b/lib/jenot/account.ex new file mode 100644 index 0000000..57e2ce7 --- /dev/null +++ b/lib/jenot/account.ex @@ -0,0 +1,16 @@ +defmodule Jenot.Account do + use Ecto.Schema + + import Ecto.Changeset + + @primary_key {:id, :binary_id, autogenerate: true} + schema "accounts" do + timestamps(type: :utc_datetime) + end + + def new() do + %__MODULE__{} + |> change() + |> put_change(:id, Ecto.UUID.generate()) + end +end diff --git a/lib/jenot/accounts.ex b/lib/jenot/accounts.ex new file mode 100644 index 0000000..a6b141f --- /dev/null +++ b/lib/jenot/accounts.ex @@ -0,0 +1,8 @@ +defmodule Jenot.Accounts do + alias Jenot.Account + alias Jenot.Repo + + def get() do + Repo.one(Account) + end +end diff --git a/lib/jenot/application.ex b/lib/jenot/application.ex index ab92e0c..b2bfcf9 100644 --- a/lib/jenot/application.ex +++ b/lib/jenot/application.ex @@ -6,6 +6,7 @@ defmodule Jenot.Application do @impl true def start(_type, _args) do children = [ + Jenot.Repo, {Bandit, plug: Jenot.Web, scheme: :http, port: 4000} ] diff --git a/lib/jenot/note.ex b/lib/jenot/note.ex new file mode 100644 index 0000000..4884a62 --- /dev/null +++ b/lib/jenot/note.ex @@ -0,0 +1,29 @@ +defmodule Jenot.Note do + use Ecto.Schema + + import Ecto.Changeset + + @primary_key {:id, :binary_id, autogenerate: true} + schema "notes" do + field(:internal_id, :string) + field(:title, :string) + field(:content, :string) + + belongs_to(:account, Jenot.Account, type: :binary_id) + + timestamps(type: :utc_datetime) + end + + def new(account, params) do + %__MODULE__{} + |> cast(params, [:internal_id, :title, :content]) + |> put_change(:id, Ecto.UUID.generate()) + |> validate_required([:internal_id]) + |> put_assoc(:account, account) + end + + def update(note, params) do + note + |> cast(params, [:title, :content]) + end +end diff --git a/lib/jenot/notes.ex b/lib/jenot/notes.ex new file mode 100644 index 0000000..e2d535e --- /dev/null +++ b/lib/jenot/notes.ex @@ -0,0 +1,80 @@ +defmodule Jenot.Notes do + import Ecto.Query + + alias Jenot.Note + alias Jenot.Repo + + def serialize(note) do + Map.take(note, [:internal_id, :title, :content, :inserted_at, :updated_at]) + end + + def latest_change(account) do + Note + |> where(account_id: ^account.id) + |> select([a], max(a.updated_at)) + |> Repo.one() + end + + def all(account, since \\ nil) do + Note + |> where(account_id: ^account.id) + |> maybe_filter_since(since) + |> Repo.all() + end + + def add(account, params) do + changeset = Note.new(account, params) + + case upsert(changeset, target: [:account_id, :internal_id]) do + {:ok, note} -> {:ok, note} + {:error, _changeset} -> {:error, :invalid_data} + end + end + + def note_by_internal_id(account, internal_id) do + note = Repo.get_by(Note, account_id: account.id, internal_id: internal_id) + + if note do + {:ok, note} + else + {:error, :note_not_found} + end + end + + def update(account, internal_id, params) do + with {:ok, note} <- note_by_internal_id(account, internal_id) do + changeset = Note.update(note, params) + + case upsert(changeset, target: [:id]) do + {:ok, note} -> {:ok, note} + {:error, _changeset} -> {:error, :invalid_data} + end + end + end + + def delete(account, internal_id) do + Note + |> where(account_id: ^account.id, internal_id: ^internal_id) + |> Repo.delete_all() + + :ok + end + + defp maybe_filter_since(query, nil), do: query + + defp maybe_filter_since(query, datetime) do + where(query, [n], n.updated_at > ^datetime) + end + + defp upsert(changeset, opts) do + conflict_target = Keyword.fetch!(opts, :target) + title = Ecto.Changeset.get_field(changeset, :title) || "" + content = Ecto.Changeset.get_field(changeset, :content) || "" + + Repo.insert(changeset, + on_conflict: [set: [title: title, content: content, updated_at: DateTime.utc_now()]], + conflict_target: conflict_target, + returning: true + ) + end +end diff --git a/lib/jenot/reminder.ex b/lib/jenot/reminder.ex new file mode 100644 index 0000000..d1e6d1d --- /dev/null +++ b/lib/jenot/reminder.ex @@ -0,0 +1,16 @@ +defmodule Jenot.Reminder do + use Ecto.Schema + + @primary_key {:id, :binary_id, autogenerate: true} + schema "reminders" do + field(:date, :date) + field(:time, :time) + field(:day_of_week, :integer) + field(:repeat_period, Ecto.Enum, values: [:day, :week, :month, :year]) + field(:repeat_count, :integer) + + belongs_to(:note, Jenot.Note) + + timestamps(type: :utc_datetime) + end +end diff --git a/lib/jenot/repo.ex b/lib/jenot/repo.ex new file mode 100644 index 0000000..51412dc --- /dev/null +++ b/lib/jenot/repo.ex @@ -0,0 +1,3 @@ +defmodule Jenot.Repo do + use Ecto.Repo, otp_app: :jenot, adapter: Ecto.Adapters.SQLite3 +end diff --git a/lib/jenot/subscription.ex b/lib/jenot/subscription.ex new file mode 100644 index 0000000..270a599 --- /dev/null +++ b/lib/jenot/subscription.ex @@ -0,0 +1,16 @@ +defmodule Jenot.Subscription do + use Ecto.Schema + + @primary_key {:id, :binary_id, autogenerate: true} + schema "subscriptions" do + field(:endpoint, :string) + field(:token, :string) + field(:auth, :string) + + field(:hash, :string) + + belongs_to(:account, Jenot.Account) + + timestamps(type: :utc_datetime) + end +end diff --git a/lib/jenot/web.ex b/lib/jenot/web.ex index 11e15d1..cace960 100644 --- a/lib/jenot/web.ex +++ b/lib/jenot/web.ex @@ -5,6 +5,9 @@ defmodule Jenot.Web do use Plug.Router + alias Jenot.Accounts + alias Jenot.Notes + if Mix.env() == :dev do plug PlugLiveReload end @@ -18,6 +21,11 @@ defmodule Jenot.Web do only: ~w(img js index.html site.webmanifest style.css), from: {:jenot, "priv/static"} + plug Plug.Parsers, + parsers: [:urlencoded, :json], + pass: ["*/*"], + json_decoder: Jason + plug :match plug :dispatch @@ -38,6 +46,71 @@ defmodule Jenot.Web do """) end + get "/api/latest" do + account = Accounts.get() + + send_resp(conn, 200, Jason.encode!(%{notes: Notes.latest_change(account)})) + end + + get "/api/notes" do + account = Accounts.get() + + notes = + account + |> Notes.all(conn.params["since"]) + |> Enum.map(&Notes.serialize/1) + + send_resp(conn, 200, Jason.encode!(notes)) + end + + post "/api/notes" do + account = Accounts.get() + + case Notes.add(account, conn.params) do + {:ok, note} -> + send_resp(conn, 200, Jason.encode!(Notes.serialize(note))) + + {:error, _} -> + send_resp(conn, 422, Jason.encode!(%{error: "Invalid input data"})) + end + end + + put "/api/notes/:internal_id" do + account = Accounts.get() + + case Notes.update(account, internal_id, conn.params) do + {:ok, note} -> + send_resp(conn, 200, Jason.encode!(Notes.serialize(note))) + + {:error, :note_not_found} -> + send_resp(conn, 422, Jason.encode!(%{error: "Note does not exist"})) + + {:error, :invalid_data} -> + send_resp(conn, 422, Jason.encode!(%{error: "Invalid input data"})) + end + end + + delete "/api/notes/:internal_id" do + account = Accounts.get() + + :ok = Notes.delete(account, internal_id) + + send_resp(conn, 204, "") + end + + get "/api/push/public-key" do + public_key = Application.fetch_env!(:web_push_elixir, :vapid_public_key) + send_resp(conn, 200, Jason.encode!(%{public_key: public_key})) + end + + post "/api/push/subscribe" do + send_resp(conn, 200, "") + end + + post "/api/push/unsubscribe" do + send_resp(conn, 200, "") + end + match _ do send_resp(conn, 404, "not found") end diff --git a/mix.exs b/mix.exs index 09dc269..85c570f 100644 --- a/mix.exs +++ b/mix.exs @@ -23,6 +23,8 @@ defmodule Jenot.MixProject do defp deps do [ {:bandit, "~> 1.0"}, + {:web_push_elixir, "~> 0.4.0"}, + {:ecto_sqlite3, "~> 0.17"}, {:plug_live_reload, "~> 0.1.0", only: :dev} ] end diff --git a/mix.lock b/mix.lock index 3846eda..4bdc27b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,17 +1,37 @@ %{ "bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"}, + "cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, + "decimal": {:hex, :decimal, "2.2.0", "df3d06bb9517e302b1bd265c1e7f16cda51547ad9d99892049340841f3e15836", [:mix], [], "hexpm", "af8daf87384b51b7e611fb1a1f2c4d4876b65ef968fa8bd3adf44cff401c7f21"}, + "ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, + "ecto_sqlite3": {:hex, :ecto_sqlite3, "0.17.4", "48dd9c6d0fc10875a64545d04f0478b142898b6f0e73ae969becf5726f834d22", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "f67372e0eae5e5cbdd1d145e78e670fc5064d5810adf99d104d364cb920e306a"}, + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, + "exqlite": {:hex, :exqlite, "0.27.0", "2ef6021862e74c6253d1fb1f5701bd47e4e779b035d34daf2a13ec83945a05ba", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "b947b9db15bb7aad11da6cd18a0d8b78f7fcce89508a27a5b9be18350fe12c59"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, + "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "plug_live_reload": {:hex, :plug_live_reload, "0.1.1", "5ddf1a08b0dcc9f3623e87492d8a517cb107f9aacb1f3524267cd142e90184b5", [:mix], [{:cowboy, "~> 2.9", [hex: :cowboy, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.1", [hex: :file_system, repo: "hexpm", optional: false]}, {:plug, "~> 1.12", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ac91b75077b9a454ef035c6a9f58cc1708a27c6a97532aa910f11224e1df1615"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "web_push_elixir": {:hex, :web_push_elixir, "0.4.0", "e4671facae5d2cadbf0c9fcebe99bac7ac5a6aad32fc47e6d3e08d82f65dfaf4", [:mix], [{:httpoison, "~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:jose, "~> 1.11", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm", "98ac602ce15916758fb11e9a81c9c26c90b001820aced03d53fe182750401f4c"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, } diff --git a/priv/repo/migrations/20241117180924_initial_schema.exs b/priv/repo/migrations/20241117180924_initial_schema.exs new file mode 100644 index 0000000..a58aa7c --- /dev/null +++ b/priv/repo/migrations/20241117180924_initial_schema.exs @@ -0,0 +1,58 @@ +defmodule Jenot.Repo.Migrations.InitialSchema do + use Ecto.Migration + + def change do + create table(:accounts, primary_key: false) do + add :id, :uuid, primary_key: true + + timestamps(type: :datetime_usec) + end + + create table(:notes, primary_key: false) do + add :id, :uuid, null: false, primary_key: true + add :internal_id, :text, null: false + add :account_id, references(:accounts, on_delete: :delete_all), null: false + + add :title, :text, null: false, default: "" + add :content, :text, null: false, default: "" + + timestamps(type: :datetime_usec) + end + + create index(:notes, [:account_id]) + create unique_index(:notes, [:account_id, :internal_id]) + + create table(:subscriptions, primary_key: false) do + add :id, :uuid, null: false, primary_key: true + + add :endpoint, :text, null: false + add :token, :text, null: false + add :auth, :text, null: false + + add :hash, :text, null: false + + add :account_id, references(:accounts, on_delete: :delete_all), null: false + + timestamps(type: :datetime_usec) + end + + create index(:subscriptions, [:account_id]) + create unique_index(:subscriptions, [:hash]) + + create table(:reminders, primary_key: false) do + add :id, :uuid, primary_key: true + + add :date, :date, null: false + add :time, :time + add :day_of_week, :integer + add :repeat_period, :text + add :repeat_count, :integer + + add :note_id, references(:note, on_delete: :delete_all), null: false + + timestamps(type: :datetime_usec) + end + + create index(:reminders, [:note_id]) + end +end diff --git a/priv/static/js/caching-worker.js b/priv/static/js/caching-worker.js index a23389e..cdc1fb1 100644 --- a/priv/static/js/caching-worker.js +++ b/priv/static/js/caching-worker.js @@ -1,6 +1,11 @@ const putInCache = async (request, response) => { - const cache = await caches.open("v1"); - await cache.put(request, response); + const requestUrl = URL.parse(request.url); + + // We don't cache API requests + if (!requestUrl.pathname.startsWith('/api/')) { + const cache = await caches.open("v1"); + await cache.put(request, response); + } }; const cacheFirst = async ({ request, fallbackUrl }) => {