Packages

CalDAV server library — mount as a Plug, bring your own storage adapter. Heavily WIP / beta software; APIs may change between releases.

Current section

Files

Jump to
ex_dav lib mix tasks ex_dav.gen.migration.ex
Raw

lib/mix/tasks/ex_dav.gen.migration.ex

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 <name>"
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