defmodule Mix.Tasks.ExDav.Gen.Migration do @shortdoc "Generate an ExDav migration" @moduledoc """ Generates Ecto migration files for ExDav schema changes. ## Usage mix ex_dav.gen.migration upgrade_to_0_4_0 ## Available migrations * `upgrade_to_0_4_0` — rename tables from `caldav_*` to `dav_*`, add `kind` and `properties` to collections, rename `ical` → `body` on resources, add `content_type` to resources. The migration is written to `priv/repo/migrations/` in the current Mix project (i.e. the consumer's app, not ex_dav itself). """ use Mix.Task @migrations %{ "upgrade_to_0_4_0" => :migration_0_4_0 } @impl Mix.Task def run([name | _]) do case Map.fetch(@migrations, name) do {:ok, fun} -> content = apply(__MODULE__, fun, []) write_migration(name, content) :error -> raise "Unknown migration: #{name}. Available: #{Enum.join(Map.keys(@migrations), ", ")}" end end def run([]) do raise "Usage: mix ex_dav.gen.migration " end defp write_migration(name, content) do migrations_dir = "priv/repo/migrations" File.mkdir_p!(migrations_dir) timestamp = timestamp() filename = "#{timestamp}_ex_dav_#{name}.exs" path = Path.join(migrations_dir, filename) File.write!(path, content) IO.puts("* creating #{path}") end defp timestamp do {{y, mo, d}, {h, mi, s}} = :calendar.universal_time() :io_lib.format("~4..0B~2..0B~2..0B~2..0B~2..0B~2..0B", [y, mo, d, h, mi, s]) |> IO.iodata_to_binary() end @doc false def migration_0_4_0 do """ defmodule ExDav.Repo.Migrations.ExDavUpgradeTo040 do use Ecto.Migration def up do # ---- rename calendars table -> collections ------------------------- rename table(:caldav_calendars), to: table(:dav_collections) # add kind column; backfill all legacy rows as :calendar alter table(:dav_collections) do add :kind, :string, null: false, default: "calendar" add :properties, :map, default: %{} end # move components array into properties['components'], drop old column execute \"\"\" UPDATE dav_collections SET properties = jsonb_build_object('components', to_jsonb(components)) WHERE components IS NOT NULL AND array_length(components, 1) > 0 \"\"\", "" alter table(:dav_collections) do remove :components end drop_if_exists unique_index(:caldav_calendars, [:user_id, :name]) create unique_index(:dav_collections, [:user_id, :name, :kind]) # ---- rename objects table -> resources -------------------------------- rename table(:caldav_calendar_objects), to: table(:dav_resources) alter table(:dav_resources) do add :content_type, :string end execute \"\"\" ALTER TABLE dav_resources RENAME COLUMN ical TO body \"\"\", \"\"\" ALTER TABLE dav_resources RENAME COLUMN body TO ical \"\"\" execute \"\"\" UPDATE dav_resources r SET content_type = 'text/calendar; charset=utf-8' \"\"\", "" # ---- tombstones table rename ---------------------------------------- rename table(:caldav_calendar_object_tombstones), to: table(:dav_tombstones) execute \"\"\" ALTER TABLE dav_tombstones RENAME COLUMN calendar_id TO collection_id \"\"\", \"\"\" ALTER TABLE dav_tombstones RENAME COLUMN collection_id TO calendar_id \"\"\" execute \"\"\" ALTER TABLE dav_resources RENAME COLUMN calendar_id TO collection_id \"\"\", \"\"\" ALTER TABLE dav_resources RENAME COLUMN collection_id TO calendar_id \"\"\" # Update FK constraint names (Postgres auto-names them) execute \"\"\" ALTER TABLE dav_resources DROP CONSTRAINT IF EXISTS caldav_calendar_objects_calendar_id_fkey \"\"\", "" execute \"\"\" ALTER TABLE dav_resources ADD CONSTRAINT dav_resources_collection_id_fkey FOREIGN KEY (collection_id) REFERENCES dav_collections(id) ON DELETE CASCADE \"\"\", "" execute \"\"\" ALTER TABLE dav_tombstones DROP CONSTRAINT IF EXISTS caldav_calendar_object_tombstones_calendar_id_fkey \"\"\", "" execute \"\"\" ALTER TABLE dav_tombstones ADD CONSTRAINT dav_tombstones_collection_id_fkey FOREIGN KEY (collection_id) REFERENCES dav_collections(id) ON DELETE CASCADE \"\"\", "" execute \"\"\" ALTER TABLE dav_collections DROP CONSTRAINT IF EXISTS caldav_calendars_user_id_fkey \"\"\", "" execute \"\"\" ALTER TABLE dav_collections ADD CONSTRAINT dav_collections_user_id_fkey FOREIGN KEY (user_id) REFERENCES caldav_users(id) ON DELETE CASCADE \"\"\", "" end def down do rename table(:dav_collections), to: table(:caldav_calendars) alter table(:caldav_calendars) do add :components, {:array, :string}, default: ["VEVENT", "VTODO"] end execute \"\"\" UPDATE caldav_calendars SET components = ARRAY(SELECT jsonb_array_elements_text(properties->'components')) WHERE properties ? 'components' \"\"\", "" alter table(:caldav_calendars) do remove :kind remove :properties end rename table(:dav_resources), to: table(:caldav_calendar_objects) execute \"\"\" ALTER TABLE caldav_calendar_objects RENAME COLUMN body TO ical \"\"\", "" execute \"\"\" ALTER TABLE caldav_calendar_objects RENAME COLUMN collection_id TO calendar_id \"\"\", "" alter table(:caldav_calendar_objects) do remove :content_type end rename table(:dav_tombstones), to: table(:caldav_calendar_object_tombstones) execute \"\"\" ALTER TABLE caldav_calendar_object_tombstones RENAME COLUMN collection_id TO calendar_id \"\"\", "" end end """ end end