From f308f714ff946168444929bbe34b658d98a70dc9 Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Thu, 18 Sep 2025 00:38:14 +0100 Subject: [PATCH 01/18] feat: add function to retrieve shifts by student id --- .../university/degrees/courses/shifts.ex | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib/atlas/university/degrees/courses/shifts.ex b/lib/atlas/university/degrees/courses/shifts.ex index c7e2205..5b0d149 100644 --- a/lib/atlas/university/degrees/courses/shifts.ex +++ b/lib/atlas/university/degrees/courses/shifts.ex @@ -5,6 +5,7 @@ defmodule Atlas.University.Degrees.Courses.Shifts do use Atlas.Context alias Atlas.University.Degrees.Courses.Shifts.Shift + alias Atlas.University.ShiftEnrollment @doc """ Returns the list of shifts. @@ -21,6 +22,33 @@ defmodule Atlas.University.Degrees.Courses.Shifts do |> Repo.all() end + @doc """ + Returns the list of shifts a student is enrolled in. + + ## Examples + + iex> list_shifts_for_student(123) + [%Shift{}, ...] + + iex> list_shifts_for_student(456) + [] + + """ + def list_shifts_for_student(student_id, opts \\ []) do + shift_ids = + from(e in ShiftEnrollment, + where: e.student_id == ^student_id, + select: e.shift_id + ) + |> Repo.all() + + Shift + |> where([s], s.id in ^shift_ids) + |> apply_filters(opts) + |> Repo.all() + |> Repo.preload([:course, :timeslots]) + end + @doc """ Gets a single shift. From 7c8c91ca5d2885da98aa5ba55255756363b35511 Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Thu, 18 Sep 2025 00:38:33 +0100 Subject: [PATCH 02/18] chore: add dependency for ics file generation --- mix.exs | 1 + mix.lock | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/mix.exs b/mix.exs index 41e0a50..af53e3d 100644 --- a/mix.exs +++ b/mix.exs @@ -64,6 +64,7 @@ defmodule Atlas.MixProject do {:igniter, "~> 0.5", only: [:dev]}, {:csv, "~> 3.2"}, {:libgraph, "~> 0.16.0"}, + {:icalendar, "~> 1.1.2"}, # monitoring {:telemetry_metrics, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index d54e4b2..fe753cd 100644 --- a/mix.lock +++ b/mix.lock @@ -3,6 +3,7 @@ "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.3.2", "d50091e3c9492d73e17fc1e1619a9b09d6a5ef99160eb4d736926fd475a16ca3", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "471be5151874ae7931911057d1467d908955f93554f7a6cd1b7d804cac8cef53"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.15", "8aa930c890fe18b6fe0a0cff27b27d0d4d231867897bd23ea772dee561f032a3", [:mix], [], "hexpm", "96ce4c69d7d5d7a0761420ef743e2f4096253931a3ba69e5ff8ef1844fe446d3"}, + "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"}, "corsica": {:hex, :corsica, "2.1.3", "dccd094ffce38178acead9ae743180cdaffa388f35f0461ba1e8151d32e190e6", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "616c08f61a345780c2cf662ff226816f04d8868e12054e68963e95285b5be8bc"}, @@ -23,17 +24,23 @@ "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "guardian": {:hex, :guardian, "2.4.0", "efbbb397ecca881bb548560169922fc4433a05bc98c2eb96a7ed88ede9e17d64", [:mix], [{:jose, "~> 1.11.9", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "5c80103a9c538fbc2505bf08421a82e8f815deba9eaedb6e734c66443154c518"}, "guardian_db": {:hex, :guardian_db, "3.0.0", "c42902e3f1af1ba1e2d0c10913b926a1421f3a7e38eb4fc382b715c17489abdb", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "9c2ec4278efa34f9f1cc6ba795e552d41fdc7ffba5319d67eeb533b89392d183"}, + "hackney": {:hex, :hackney, "1.25.0", "390e9b83f31e5b325b9f43b76e1a785cbdb69b5b6cd4e079aa67835ded046867", [:rebar3], [{:certifi, "~> 2.15.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.4", [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.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "7209bfd75fd1f42467211ff8f59ea74d6f2a9e81cbcee95a56711ee79fd6b1d4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "icalendar": {:hex, :icalendar, "1.1.2", "5d0afff5d0143c5bd43f18ae32a777bf0fb9a724543ab05229a460d368f0a5e7", [:mix], [{:timex, "~> 3.4", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "2060f8e353fdf3047e95a3f012583dc3c0bbd7ca1010e32ed9e9fc5760ad4292"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "igniter": {:hex, :igniter, "0.6.27", "a7c01062db56f5c5ac0f36ff8ef3cce1d61cd6bf59e50c52f4a38dc926aa9728", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d1eda5271932dcb9f00f94936c3dc12a2b96466f895f4b3fb82a0caada6d6447"}, "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"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "oban": {:hex, :oban, "2.20.1", "39d0b68787e5cf251541c0d657a698f6142a24d8744e1e40b2cf045d4fa232a6", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.20", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17a45277dbeb41a455040b41dd8c467163fad685d1366f2f59207def3bcdd1d8"}, "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.21", "14ca4f1071a5f65121217d6b57ac5712d1857e40a0833aff7a691b7870fc9a3b", [: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", "336dce4f86cba56fed312a7d280bf2282c720abb6074bdb1b61ec8095bdd0bc9"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.5", "c4ef322acd15a574a8b1a08eff0ee0a85e73096b53ce1403b6563709f15e1cea", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "26ec3208eef407f31b748cadd044045c6fd485fbff168e35963d2f9dfff28d4b"}, "phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"}, @@ -53,13 +60,17 @@ "saxy": {:hex, :saxy, "1.6.0", "02cb4e9bd045f25ac0c70fae8164754878327ee393c338a090288210b02317ee", [:mix], [], "hexpm", "ef42eb4ac983ca77d650fbdb68368b26570f6cc5895f0faa04d34a6f384abad3"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "swoosh": {:hex, :swoosh, "1.17.10", "3bfce0e716f92c85579c8b7bb390f1d287f388e4961bfb9343fe191ec4214225", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "277f86c249089f4fc7d70944987151b76424fac1d348d40685008ba88e0a2717"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "thousand_island": {:hex, :thousand_island, "1.4.1", "8df065e627407e281f7935da5ad0f3842d10eb721afa92e760b720d71e2e37aa", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "204a8640e5d2818589b87286ae66160978628d7edf6095181cbe0440765fb6c1"}, + "timex": {:hex, :timex, "3.7.13", "0688ce11950f5b65e154e42b47bf67b15d3bc0e0c3def62199991b8a8079a1e2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "09588e0522669328e973b8b4fd8741246321b3f0d32735b589f78b136e6d4c54"}, + "tzdata": {:hex, :tzdata, "1.1.3", "b1cef7bb6de1de90d4ddc25d33892b32830f907e7fc2fccd1e7e22778ab7dfbc", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "d4ca85575a064d29d4e94253ee95912edfb165938743dbf002acdf0dcecb0c28"}, "ua_parser": {:hex, :ua_parser, "1.9.3", "1c3191ac62a6f3663b9c213ae5e1faef5dc03e29b6edbe34731a8f2f07802467", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "17e1b46cee8c2b49a4f9edec7ecb822846d4974cbd84ce02cbc169cdf1f58dfb"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [: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", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, "xlsx_reader": {:hex, :xlsx_reader, "0.8.8", "fbb29049548ff687f03a2873f2eb0d9057e47eb69cafb07f44988f030fb620b7", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:saxy, "~> 1.5", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "642d979a3a156b150bb76a89998a130483e1c399fa32e8d3a66abc1d9799dbd7"}, From 5733409fe9da4c16e4c0bcef3c3e93aff8c445db Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Thu, 18 Sep 2025 00:39:00 +0100 Subject: [PATCH 03/18] feat: add ics generator module --- lib/atlas/calendar.ex | 139 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 lib/atlas/calendar.ex diff --git a/lib/atlas/calendar.ex b/lib/atlas/calendar.ex new file mode 100644 index 0000000..04229e3 --- /dev/null +++ b/lib/atlas/calendar.ex @@ -0,0 +1,139 @@ +defmodule Atlas.Calendar do + @moduledoc """ + iCalendar (.ics) file generator for Atlas. + """ + + @prodid "-//Atlas//EN" + + defp format_datetime(%NaiveDateTime{} = dt) do + dt + |> NaiveDateTime.to_iso8601() + |> String.replace(~r/[-:]/, "") + |> String.replace(~r/\.\d+Z?/, "") + end + + @doc """ + Converts a list of shifts into `.ics` calendar content. + + Each shift should be preloaded with :timeslots and :course associations. + """ + def shifts_to_ics(shifts, opts \\ []) do + uid_prefix = Keyword.get(opts, :uid_prefix, "atlas") + cal_name = Keyword.get(opts, :calendar_name, "Atlas Calendar") + dtstamp = format_datetime(NaiveDateTime.utc_now()) + + events = + shifts + |> Enum.with_index() + |> Enum.flat_map(fn {shift, idx} -> + shift.timeslots + |> Enum.map(fn ts -> + uid = "#{uid_prefix}-#{shift_uid(shift, idx)}-#{ts.id || "ts"}" + {dtstart, dtend} = extract_start_end(ts) + + """ + BEGIN:VEVENT + UID:#{uid} + DTSTAMP:#{dtstamp} + DTSTART:#{format_datetime(dtstart)} + DTEND:#{format_datetime(dtend)} + SUMMARY:#{escape_text(build_summary(shift))} + DESCRIPTION:#{escape_text(build_description(shift, ts))} + LOCATION:#{escape_text(location_of(ts))} + END:VEVENT + """ + end) + end) + |> Enum.join("\r\n") + + [ + "BEGIN:VCALENDAR", + "VERSION:2.0", + "PRODID:#{@prodid}", + "CALSCALE:GREGORIAN", + "METHOD:PUBLISH", + "X-WR-CALNAME:#{cal_name}", + events, + "END:VCALENDAR" + ] + |> Enum.join("\r\n") + end + + # --- Helpers --------------------------------------------------------------- + + # WIP: This function is a patch for the time being as the module does not allow for proper repetiveness atm + defp extract_start_end(%{start: start_time, end: end_time, weekday: weekday}) do + today = Date.utc_today() + # 1 = Monday ... 7 = Sunday + today_weekday = Date.day_of_week(today) + target_weekday = weekday_to_int(weekday) + + # Days until the next occurrence of the weekday + days_ahead = rem(target_weekday - today_weekday + 7, 7) + # always next week if today + days_ahead = if days_ahead == 0, do: 7, else: days_ahead + + event_date = Date.add(today, days_ahead) + + { + NaiveDateTime.new!(event_date, start_time), + NaiveDateTime.new!(event_date, end_time) + } + end + + defp weekday_to_int(:monday), do: 1 + defp weekday_to_int(:tuesday), do: 2 + defp weekday_to_int(:wednesday), do: 3 + defp weekday_to_int(:thursday), do: 4 + defp weekday_to_int(:friday), do: 5 + defp weekday_to_int(:saturday), do: 6 + defp weekday_to_int(:sunday), do: 7 + + defp shift_uid(shift, idx), do: Map.get(shift, :id, idx) + + defp build_summary(shift) do + course_name = + if shift.course && shift.course.name do + shift.course.name + else + "Course" + end + + "#{course_name} – #{Atlas.University.Degrees.Courses.Shifts.Shift.short_name(shift)}" + end + + defp build_description(shift, ts) do + {st, en} = extract_start_end(ts) + + time_range = + "#{String.slice(Time.to_iso8601(NaiveDateTime.to_time(st)), 0, 5)} - " <> + String.slice(Time.to_iso8601(NaiveDateTime.to_time(en)), 0, 5) + + professor = + if is_binary(shift.professor), do: shift.professor, else: "" + + """ + Shift #{Atlas.University.Degrees.Courses.Shifts.Shift.short_name(shift)} + Time: #{time_range} + Location: #{location_of(ts)} + Professor: #{professor} + """ + end + + defp location_of(ts) do + cond do + ts.building && ts.room -> "#{ts.building} #{ts.room}" + true -> "Unspecified location" + end + end + + defp escape_text(nil), do: "" + + defp escape_text(text) when is_binary(text) do + text + |> String.replace("\r\n", "\\n") + |> String.replace("\n", "\\n") + |> String.replace(",", "\\,") + |> String.replace(";", "\\;") + end +end From 7fd577aa2d2e9eda3d30aaa1d82f8b14fcecc9c9 Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Thu, 18 Sep 2025 00:39:06 +0100 Subject: [PATCH 04/18] feat: add route for calendar export --- lib/atlas_web/router.ex | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/atlas_web/router.ex b/lib/atlas_web/router.ex index 6cd37b5..099a2ef 100644 --- a/lib/atlas_web/router.ex +++ b/lib/atlas_web/router.ex @@ -83,6 +83,12 @@ defmodule AtlasWeb.Router do resources "/", ShiftExchangeRequestController, only: [:index, :create, :show, :delete] end + scope "/export" do + scope "/student" do + get "/:student_id/calendar.ics", CalendarExportController, :student_calendar + end + end + pipe_through :is_at_least_professor scope "/jobs" do From 1436f5fb6cca54ce4e527ad234a4cf499523ee7d Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Thu, 18 Sep 2025 00:39:10 +0100 Subject: [PATCH 05/18] feat: add controller for calendar export --- .../controllers/calendar_export_controller.ex | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 lib/atlas_web/controllers/calendar_export_controller.ex diff --git a/lib/atlas_web/controllers/calendar_export_controller.ex b/lib/atlas_web/controllers/calendar_export_controller.ex new file mode 100644 index 0000000..20ac23c --- /dev/null +++ b/lib/atlas_web/controllers/calendar_export_controller.ex @@ -0,0 +1,22 @@ +defmodule AtlasWeb.CalendarExportController do + use AtlasWeb, :controller + + alias Atlas.University.Degrees.Courses.Shifts + alias Atlas.Calendar + + @doc """ + Exports a student's full schedule as an `.ics` file. + """ + def student_calendar(conn, %{"student_id" => student_id}) do + shifts = Shifts.list_shifts_for_student(student_id) + ics_content = Calendar.shifts_to_ics(shifts, calendar_name: "Student #{student_id} Schedule") + + conn + |> put_resp_content_type("text/calendar; charset=utf-8") + |> put_resp_header( + "content-disposition", + ~s[attachment; filename="student-#{student_id}-calendar.ics"] + ) + |> send_resp(200, ics_content) + end +end From 21c5028860fa503f034925cddbc79c77f8c84041 Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Thu, 18 Sep 2025 09:28:49 +0100 Subject: [PATCH 06/18] fix: linting --- lib/atlas/calendar.ex | 13 ++++++++----- .../controllers/calendar_export_controller.ex | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/atlas/calendar.ex b/lib/atlas/calendar.ex index 04229e3..10a7414 100644 --- a/lib/atlas/calendar.ex +++ b/lib/atlas/calendar.ex @@ -5,6 +5,8 @@ defmodule Atlas.Calendar do @prodid "-//Atlas//EN" + alias Atlas.University.Degrees.Courses.Shifts.Shift + defp format_datetime(%NaiveDateTime{} = dt) do dt |> NaiveDateTime.to_iso8601() @@ -99,7 +101,7 @@ defmodule Atlas.Calendar do "Course" end - "#{course_name} – #{Atlas.University.Degrees.Courses.Shifts.Shift.short_name(shift)}" + "#{course_name} – #{Shift.short_name(shift)}" end defp build_description(shift, ts) do @@ -113,7 +115,7 @@ defmodule Atlas.Calendar do if is_binary(shift.professor), do: shift.professor, else: "" """ - Shift #{Atlas.University.Degrees.Courses.Shifts.Shift.short_name(shift)} + Shift #{Shift.short_name(shift)} Time: #{time_range} Location: #{location_of(ts)} Professor: #{professor} @@ -121,9 +123,10 @@ defmodule Atlas.Calendar do end defp location_of(ts) do - cond do - ts.building && ts.room -> "#{ts.building} #{ts.room}" - true -> "Unspecified location" + if ts.building && ts.room do + "#{ts.building} #{ts.room}" + else + "Unspecified location" end end diff --git a/lib/atlas_web/controllers/calendar_export_controller.ex b/lib/atlas_web/controllers/calendar_export_controller.ex index 20ac23c..bfa211f 100644 --- a/lib/atlas_web/controllers/calendar_export_controller.ex +++ b/lib/atlas_web/controllers/calendar_export_controller.ex @@ -1,8 +1,8 @@ defmodule AtlasWeb.CalendarExportController do use AtlasWeb, :controller - alias Atlas.University.Degrees.Courses.Shifts alias Atlas.Calendar + alias Atlas.University.Degrees.Courses.Shifts @doc """ Exports a student's full schedule as an `.ics` file. From 0697618fd8b6f7a7e58ede1298f5a24ee58aa48d Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Fri, 19 Sep 2025 17:48:08 +0100 Subject: [PATCH 07/18] fix: add repeating event rule --- lib/atlas/calendar.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/atlas/calendar.ex b/lib/atlas/calendar.ex index 10a7414..563d4c3 100644 --- a/lib/atlas/calendar.ex +++ b/lib/atlas/calendar.ex @@ -42,6 +42,7 @@ defmodule Atlas.Calendar do SUMMARY:#{escape_text(build_summary(shift))} DESCRIPTION:#{escape_text(build_description(shift, ts))} LOCATION:#{escape_text(location_of(ts))} + RRULE:FREQ=WEEKLY;INTERVAL=1 END:VEVENT """ end) From 69a10e7a6baa3625605a8dbd1bb3df0fd5b22abb Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Sat, 20 Sep 2025 12:04:08 +0100 Subject: [PATCH 08/18] feat: get student by user id --- lib/atlas/university.ex | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/atlas/university.ex b/lib/atlas/university.ex index 2ddcbf6..47c211d 100644 --- a/lib/atlas/university.ex +++ b/lib/atlas/university.ex @@ -60,6 +60,23 @@ defmodule Atlas.University do Repo.get_by!(Student, number: number) end + @doc """ + Gets a single student by `user_id`. + + Returns `nil` if no student exists for the given user. + + ## Examples + + iex> get_student_by_user_id(123) + %Student{} + + iex> get_student_by_user_id(999) + nil + """ + def get_student_by_user_id(user_id) do + Repo.get_by(Student, user_id: user_id) + end + @doc """ Creates a student. From 681ff994e2acdf7e8e90865e983d7b2e3135cfab Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Sat, 20 Sep 2025 12:08:37 +0100 Subject: [PATCH 09/18] feat: make token generator public and add calendar token support --- lib/atlas_web/controllers/auth/auth_controller.ex | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/atlas_web/controllers/auth/auth_controller.ex b/lib/atlas_web/controllers/auth/auth_controller.ex index 448845b..a7a8b5b 100644 --- a/lib/atlas_web/controllers/auth/auth_controller.ex +++ b/lib/atlas_web/controllers/auth/auth_controller.ex @@ -209,7 +209,7 @@ defmodule AtlasWeb.AuthController do ) end - defp generate_token(user, session, :access) do + def generate_token(user, session, :access) do {:ok, token, _claims} = Guardian.encode_and_sign({user, session}, %{aud: @audience}, token_type: "access", @@ -219,7 +219,17 @@ defmodule AtlasWeb.AuthController do token end - defp generate_token(user, session, :refresh) do + def generate_token(user, session, :calendar) do + {:ok, token, _claims} = + Guardian.encode_and_sign({user, session}, %{aud: @audience}, + token_type: "calendar", + ttl: {10, :minute} + ) + + token + end + + def generate_token(user, session, :refresh) do {:ok, token, _claims} = Guardian.encode_and_sign({user, session}, %{aud: @audience}, token_type: "refresh", From afed948f1c75da4e897ba2ebd755f9516724619e Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Sat, 20 Sep 2025 12:13:38 +0100 Subject: [PATCH 10/18] feat: add calendar URL generator and export via short-lived calendar token --- .../controllers/calendar_export_controller.ex | 62 +++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/lib/atlas_web/controllers/calendar_export_controller.ex b/lib/atlas_web/controllers/calendar_export_controller.ex index bfa211f..7ecef13 100644 --- a/lib/atlas_web/controllers/calendar_export_controller.ex +++ b/lib/atlas_web/controllers/calendar_export_controller.ex @@ -2,21 +2,59 @@ defmodule AtlasWeb.CalendarExportController do use AtlasWeb, :controller alias Atlas.Calendar + alias Atlas.Accounts.Guardian + alias AtlasWeb.AuthController + alias Atlas.University alias Atlas.University.Degrees.Courses.Shifts + @audience "astra" + + @doc """ + Returns a short-lived signed URL to export the current user's calendar. + """ + def calendar_url(conn, _params) do + {user, session} = Guardian.Plug.current_resource(conn) + + if is_nil(user) do + conn + |> put_status(:unauthorized) + |> json(%{error: "Not authenticated"}) + else + token = + AuthController.generate_token(user, session, :calendar) + + url = "http://localhost:4000/v1/export/student/calendar.ics?token=#{token}" # FIX ME: Change to the actual api url 'pombo.cesium.pt/api' afaik + + conn + |> json(%{calendar_url: url}) + end + end + @doc """ - Exports a student's full schedule as an `.ics` file. + Exports the current user's schedule as an `.ics` file, given a valid calendar token. """ - def student_calendar(conn, %{"student_id" => student_id}) do - shifts = Shifts.list_shifts_for_student(student_id) - ics_content = Calendar.shifts_to_ics(shifts, calendar_name: "Student #{student_id} Schedule") - - conn - |> put_resp_content_type("text/calendar; charset=utf-8") - |> put_resp_header( - "content-disposition", - ~s[attachment; filename="student-#{student_id}-calendar.ics"] - ) - |> send_resp(200, ics_content) + def student_calendar(conn, %{"token" => token}) do + with {:ok, claims} <- Guardian.decode_and_verify(token, %{"typ" => "calendar", "aud" => @audience}), + {:ok, {user, _session}} <- Guardian.resource_from_claims(claims), + student <- University.get_student_by_user_id(user.id), + %{} = student <- student do + shifts = Shifts.list_shifts_for_student(student.id) + + ics_content = + Calendar.shifts_to_ics(shifts, calendar_name: "Student #{user.name} Schedule") + + conn + |> put_resp_content_type("text/calendar; charset=utf-8") + |> put_resp_header( + "content-disposition", + ~s[attachment; filename="student-#{user.name}-calendar.ics"] + ) + |> send_resp(200, ics_content) + else + _ -> + conn + |> put_status(:unauthorized) + |> json(%{error: "Invalid or expired calendar token"}) + end end end From d2150c71900a80164248624e6e7ecd597fbf6105 Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Sat, 20 Sep 2025 12:17:15 +0100 Subject: [PATCH 11/18] feat: add routes for calendar URL generation and calendar export --- lib/atlas_web/router.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/atlas_web/router.ex b/lib/atlas_web/router.ex index 099a2ef..8cfe16d 100644 --- a/lib/atlas_web/router.ex +++ b/lib/atlas_web/router.ex @@ -39,6 +39,12 @@ defmodule AtlasWeb.Router do post "/reset_password", AuthController, :reset_password end + scope "/export" do + scope "/student" do + get "/calendar.ics", CalendarExportController, :student_calendar + end + end + # Authenticated routes pipe_through :auth @@ -85,7 +91,7 @@ defmodule AtlasWeb.Router do scope "/export" do scope "/student" do - get "/:student_id/calendar.ics", CalendarExportController, :student_calendar + get "/calendarUrl", CalendarExportController, :calendar_url end end From b7414081c6c7f3b07acfac625b8aecd528b4db7c Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Sat, 20 Sep 2025 12:21:27 +0100 Subject: [PATCH 12/18] fix: imrpove wording in calendar url route --- lib/atlas_web/router.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/atlas_web/router.ex b/lib/atlas_web/router.ex index 8cfe16d..ece4e16 100644 --- a/lib/atlas_web/router.ex +++ b/lib/atlas_web/router.ex @@ -91,7 +91,7 @@ defmodule AtlasWeb.Router do scope "/export" do scope "/student" do - get "/calendarUrl", CalendarExportController, :calendar_url + get "/calendar-url", CalendarExportController, :calendar_url end end From c01bb41ee132ecdde1f0ebb7970b8c3f73a652c5 Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Sun, 21 Sep 2025 00:33:09 +0100 Subject: [PATCH 13/18] fix: add support for api url env variable --- .env.dev.sample | 3 ++- config/runtime.exs | 11 +++++++++++ .../controllers/calendar_export_controller.ex | 4 +++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.env.dev.sample b/.env.dev.sample index 0651ee5..81d9e97 100644 --- a/.env.dev.sample +++ b/.env.dev.sample @@ -1,2 +1,3 @@ export FRONTEND_URL=http://localhost:3000 -export KEPLER_API_URL=http://localhost:8000/api/v1 \ No newline at end of file +export KEPLER_API_URL=http://localhost:8000/api/v1 +export ATLAS_API_URL=http://localhost:4000 \ No newline at end of file diff --git a/config/runtime.exs b/config/runtime.exs index a54f170..b596af5 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -24,6 +24,17 @@ config :atlas, :frontend_url, System.get_env("FRONTEND_URL", "http://localhost:3 config :atlas, :kepler_api_url, System.get_env("KEPLER_API_URL", "http://localhost:8000/api/v1") +atlas_api_url = + System.get_env("ATLAS_API_URL") || + raise """ + environment variable ATLAS_API_URL is missing. + It should be the base URL of your Atlas API instance, e.g.: + http://localhost:4000 in dev + https://pombo.cesium.pt/api in prod + """ + +config :atlas, :api_url, atlas_api_url + config :atlas, from_email_name: System.get_env("FROM_EMAIL_NAME") || "Pombo", from_email_address: System.get_env("FROM_EMAIL_ADDRESS") || "no-reply@pombo.cesium.pt" diff --git a/lib/atlas_web/controllers/calendar_export_controller.ex b/lib/atlas_web/controllers/calendar_export_controller.ex index 7ecef13..02e2288 100644 --- a/lib/atlas_web/controllers/calendar_export_controller.ex +++ b/lib/atlas_web/controllers/calendar_export_controller.ex @@ -23,7 +23,9 @@ defmodule AtlasWeb.CalendarExportController do token = AuthController.generate_token(user, session, :calendar) - url = "http://localhost:4000/v1/export/student/calendar.ics?token=#{token}" # FIX ME: Change to the actual api url 'pombo.cesium.pt/api' afaik + base_url = Application.get_env(:atlas, :api_url) + + url = "#{base_url}/v1/export/student/calendar.ics?token=#{token}" conn |> json(%{calendar_url: url}) From fe297deccbc268c9e4a56b1efabc31e8a4fdbd23 Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Sun, 21 Sep 2025 00:47:43 +0100 Subject: [PATCH 14/18] chore: format --- lib/atlas_web/controllers/calendar_export_controller.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/atlas_web/controllers/calendar_export_controller.ex b/lib/atlas_web/controllers/calendar_export_controller.ex index 02e2288..6fb3725 100644 --- a/lib/atlas_web/controllers/calendar_export_controller.ex +++ b/lib/atlas_web/controllers/calendar_export_controller.ex @@ -36,7 +36,8 @@ defmodule AtlasWeb.CalendarExportController do Exports the current user's schedule as an `.ics` file, given a valid calendar token. """ def student_calendar(conn, %{"token" => token}) do - with {:ok, claims} <- Guardian.decode_and_verify(token, %{"typ" => "calendar", "aud" => @audience}), + with {:ok, claims} <- + Guardian.decode_and_verify(token, %{"typ" => "calendar", "aud" => @audience}), {:ok, {user, _session}} <- Guardian.resource_from_claims(claims), student <- University.get_student_by_user_id(user.id), %{} = student <- student do From fee3b8c1a087e104520fbd69ef7d9e5671d75555 Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Sun, 21 Sep 2025 01:04:10 +0100 Subject: [PATCH 15/18] fix: api rul env var setup --- config/runtime.exs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/config/runtime.exs b/config/runtime.exs index b596af5..c208747 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -25,13 +25,17 @@ config :atlas, :frontend_url, System.get_env("FRONTEND_URL", "http://localhost:3 config :atlas, :kepler_api_url, System.get_env("KEPLER_API_URL", "http://localhost:8000/api/v1") atlas_api_url = - System.get_env("ATLAS_API_URL") || - raise """ - environment variable ATLAS_API_URL is missing. - It should be the base URL of your Atlas API instance, e.g.: - http://localhost:4000 in dev - https://pombo.cesium.pt/api in prod - """ + case config_env() do + :test -> "http://localhost:4000" + _ -> + System.get_env("ATLAS_API_URL") || + raise """ + environment variable ATLAS_API_URL is missing. + It should be the base URL of your Atlas API instance, e.g.: + http://localhost:4000 in dev + https://pombo.cesium.pt/api in prod + """ + end config :atlas, :api_url, atlas_api_url @@ -121,6 +125,15 @@ if config_env() == :prod do config :atlas, :kepler_api_url, kepler_api_url + atlas_api_url = + System.get_env("ATLAS_API_URL") || + raise """ + environment variable ATLAS_API_URL is missing. + It should be the base URL of your Atlas API instance, e.g., http://localhost:4000/api + """ + + config :atlas, :api_url, atlas_api_url + # Configures CORS allowed origins config :atlas, :allowed_origins, frontend_url From 3f7f86a239122f32d4fdb7e2c548ff000bef8730 Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Sun, 21 Sep 2025 01:05:03 +0100 Subject: [PATCH 16/18] chore: organize modules aliases --- lib/atlas_web/controllers/calendar_export_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/atlas_web/controllers/calendar_export_controller.ex b/lib/atlas_web/controllers/calendar_export_controller.ex index 6fb3725..a61498a 100644 --- a/lib/atlas_web/controllers/calendar_export_controller.ex +++ b/lib/atlas_web/controllers/calendar_export_controller.ex @@ -1,9 +1,9 @@ defmodule AtlasWeb.CalendarExportController do use AtlasWeb, :controller - alias Atlas.Calendar alias Atlas.Accounts.Guardian alias AtlasWeb.AuthController + alias Atlas.Calendar alias Atlas.University alias Atlas.University.Degrees.Courses.Shifts From 87f8b87a8a52ddcc5d12e2f7e973347bf41245af Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Sun, 21 Sep 2025 01:07:08 +0100 Subject: [PATCH 17/18] chore: fomerat --- config/runtime.exs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/runtime.exs b/config/runtime.exs index c208747..4812810 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -26,7 +26,9 @@ config :atlas, :kepler_api_url, System.get_env("KEPLER_API_URL", "http://localho atlas_api_url = case config_env() do - :test -> "http://localhost:4000" + :test -> + "http://localhost:4000" + _ -> System.get_env("ATLAS_API_URL") || raise """ From 608abc24f95d352c05d0da3e7ffeebf96dfe5789 Mon Sep 17 00:00:00 2001 From: GuilhermePSF Date: Sun, 21 Sep 2025 01:09:01 +0100 Subject: [PATCH 18/18] fix: bs --- lib/atlas_web/controllers/calendar_export_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/atlas_web/controllers/calendar_export_controller.ex b/lib/atlas_web/controllers/calendar_export_controller.ex index a61498a..592a79f 100644 --- a/lib/atlas_web/controllers/calendar_export_controller.ex +++ b/lib/atlas_web/controllers/calendar_export_controller.ex @@ -2,10 +2,10 @@ defmodule AtlasWeb.CalendarExportController do use AtlasWeb, :controller alias Atlas.Accounts.Guardian - alias AtlasWeb.AuthController alias Atlas.Calendar alias Atlas.University alias Atlas.University.Degrees.Courses.Shifts + alias AtlasWeb.AuthController @audience "astra"