{"id":15010087,"url":"https://github.com/zenneriot/ex_audit","last_synced_at":"2025-05-15T17:01:41.802Z","repository":{"id":26121245,"uuid":"107399932","full_name":"ZennerIoT/ex_audit","owner":"ZennerIoT","description":"Ecto auditing library that transparently tracks changes and can revert them.","archived":false,"fork":false,"pushed_at":"2023-12-08T20:17:08.000Z","size":141,"stargazers_count":369,"open_issues_count":22,"forks_count":111,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-31T20:11:22.390Z","etag":null,"topics":["audit","diff","ecto","elixir","patch","revert","rollback"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ZennerIoT.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-18T11:40:10.000Z","updated_at":"2025-03-16T14:50:55.000Z","dependencies_parsed_at":"2024-06-21T04:10:41.487Z","dependency_job_id":null,"html_url":"https://github.com/ZennerIoT/ex_audit","commit_stats":{"total_commits":88,"total_committers":19,"mean_commits":4.631578947368421,"dds":"0.44318181818181823","last_synced_commit":"e130f28c667b985f105336b2ba105623f721b587"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZennerIoT%2Fex_audit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZennerIoT%2Fex_audit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZennerIoT%2Fex_audit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZennerIoT%2Fex_audit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ZennerIoT","download_url":"https://codeload.github.com/ZennerIoT/ex_audit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247730069,"owners_count":20986404,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audit","diff","ecto","elixir","patch","revert","rollback"],"created_at":"2024-09-24T19:29:59.293Z","updated_at":"2025-04-07T21:15:42.788Z","avatar_url":"https://github.com/ZennerIoT.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ExAudit\n\nEcto auditing library that transparently tracks changes and can revert them.\n\nExAudit plugs right into your ecto repositories and hooks all the data mutating Ecto.Repo functions\nto track changes to entities in your database.\n\n## Features\n\n- Wraps Ecto.Repo, no need to change your existing codebase to start tracking changes\n- Creates +- diffs of the casted structs. Custom types are automatically supported.\n- Ships with functions to review the history of a struct and roll back changes\n- Allows custom ID types and custom fields in the version schema\n- Tracks associated entities when they're created, updated or deleted in a single Repo call\n- Recursively tracks cascading deletions\n\n## Usage\n\nExAudit replaces some functions in your repo module:\n\n- `insert/2`\n- `insert!/2`\n- `update/2`\n- `update!/2`\n- `delete/2`\n- `delete!/2`\n\nAll changes to the database made with these functions will automatically be tracked.\n\nAlso, new functions are added to the repository:\n\n- `history/2`: lists all versions of the given struct ordered from oldest to newest\n- `revert/2`: rolls the referenced entity back to the state it was before the given version\n  was changed\n\nWith this API, you should be able to enable auditing across your entire application easily.\n\nIf for some reason ExAudit does not track a change, you can manually add it with\n`ExAudit.Tracking.track_change(module, adapter, action, changeset, resulting_struct, opts)`.\n\nIn the same module, there are a few other functions you might find useful to roll custom\ntracking.\n\n## Setup\n\nAdd ex_audit to your list of dependencies:\n\n```elixir\ndef deps do\n  [\n    {:ex_audit, \"~\u003e 0.9\"}\n  ]\nend\n```\n\nFor older ecto versions than 3.2, check out what to do in the [Ecto Versions](#ecto-versions) section.\n\nYou have to hook `ExAudit.Repo` to your repo:\n\n```elixir\ndefmodule MyApp.Repo do\n  use Ecto.Repo,\n    otp_app: :my_app,\n    adapter: Ecto.Adapters.Postgres\n\n  use ExAudit.Repo\nend\n```\n\n### Configuration\n\nYou have to tell ExAudit which schemas to track and the module of your version schema.\n\nIn your config.exs, write something like this:\n\n```elixir\nconfig :ex_audit,\n  ecto_repos: [MyApp.Repo],\n  version_schema: MyApp.Version,\n  tracked_schemas: [\n    MyApp.Accounts.User,\n    MyApp.BlogPost,\n    MyApp.Comment\n  ]\n```\n\nOptionally, you can tell ExAudit to treat certain structs as primitives and not record internal changes for the \nstruct. Add these under the key `:primitive_structs` in your config. So for example, if you configured `Date` to be treated as a primitive:\n\n```elixir\nconfig :ex_audit,\n  ecto_repos: [MyApp.Repo],\n  version_schema: MyApp.Version,\n  tracked_schemas: [\n    MyApp.Accounts.User,\n    MyApp.BlogPost,\n    MyApp.Comment\n  ],\n  primitive_structs: [\n    Date\n  ]\n```\n\nthen the patch would record the entire Date struct as a change:\n\n```elixir\n{:primitive_change, ~D[2000-01-01], ~D[2000-01-18]}\n```\n\ninstead of descending into the struct to find the individual part that changed:\n\n```elixir\n{:changed, %{day: {:changed, {:primitive_change, 1, 18}}}}\n```\n\n### Version Schema and Migration\n\nYou need to copy the migration and the schema module for the versions table. This allows you to add custom fields\nto the table or decide which type to use for the primary key.\n\n#### `version.ex`\n\n```elixir\ndefmodule MyApp.Version do\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  schema \"versions\" do\n    # The patch in Erlang External Term Format\n    field :patch, ExAudit.Type.Patch\n\n    # supports UUID and other types as well\n    field :entity_id, :integer\n\n    # name of the table the entity is in\n    field :entity_schema, ExAudit.Type.Schema\n\n    # type of the action that has happened to the entity (created, updated, deleted)\n    field :action, ExAudit.Type.Action\n\n    # when has this happened\n    field :recorded_at, :utc_datetime\n\n    # was this change part of a rollback?\n    field :rollback, :boolean, default: false\n\n    # custom fields\n    belongs_to :user, MyApp.Accounts.User\n  end\n\n  def changeset(struct, params \\\\ %{}) do\n    struct\n    |\u003e cast(params, [:patch, :entity_id, :entity_schema, :action, :recorded_at, :rollback])\n    |\u003e cast(params, [:user_id]) # custom fields\n  end\nend\n```\n\n#### `create_version_table.exs`\n\n```elixir\ndefmodule MyApp.Migrations.AddVersions do\n  use Ecto.Migration\n\n  def change do\n    create table(:versions) do\n      # The patch in Erlang External Term Format\n      add :patch, :binary\n\n      # supports UUID and other types as well\n      add :entity_id, :integer\n\n      # name of the table the entity is in\n      add :entity_schema, :string\n\n      # type of the action that has happened to the entity (created, updated, deleted)\n      add :action, :string\n\n      # when has this happened\n      add :recorded_at, :utc_datetime\n\n      # was this change part of a rollback?\n      add :rollback, :boolean, default: false\n\n      # optional fields that you can define yourself\n      # for example, it's a good idea to track who did the change\n      add :user_id, references(:users, on_update: :update_all, on_delete: :nilify_all)\n    end\n\n    # create this if you are going to have more than a hundred of thousands of versions\n    create index(:versions, [:entity_schema, :entity_id])\n  end\nend\n```\n\n### Recording custom data\n\nIf you want to track custom data such as the user id, you can simply pass a keyword list with that data\nto the `:ex_audit_custom` option in any Repo function:\n\n```elixir\nMyApp.Repo.insert(changeset, ex_audit_custom: [user_id: conn.assigns.current_user.id])\n```\n\nOf course it is tedious to upgrade your entire codebase just to track the user ID for example, so you can\nalso pass this data in a plug:\n\n```elixir\ndefmodule MyApp.ExAuditPlug do\n  def init(_) do\n    nil\n  end\n\n  def call(conn, _) do\n    ExAudit.track(user_id: conn.assigns.current_user.id)\n    conn\n  end\nend\n```\n\nIn the background, ExAudit.track will remember the PID it was called from and attaches the passed data to that\nPID. In most cases, the conn process will call the Repo functions, so ExAudit can get the data from that PID again deeper\nin the plug tree.\n\nIn some cases where it is not possible to call the Repo function from the conn process, you have to pass the\ncustom data manually via the options described above.\n\nExamples for data you might want to track additionally:\n\n- User ID\n- API Key ID\n- Message from the user describing what she changed\n\n## Ecto versions\n\nFor ecto 2.x, use `{:ex_audit, \"~\u003e 0.5\"}`\n\nFor ecto 3.0, upgrade ecto to 3.1\n\nFor ecto 3.1, use `{:ex_audit, \"~\u003e 0.6\"}`\n\nFor ecto 3.1.2 or higher, upgrade ecto to 3.2\n\nFor ecto 3.2, use `{:ex_audit, \"~\u003e 0.7\"}`\n\n## More\n\nThe documentation is available at [https://hexdocs.pm/ex_audit](https://hexdocs.pm/ex_audit).\n\nCheck out [ZENNER IoT Solutions](https://zenner-iot.com/), makers of the [ELEMENT IoT platform](https://zenner-iot.com/iot-plattform).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzenneriot%2Fex_audit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzenneriot%2Fex_audit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzenneriot%2Fex_audit/lists"}