{"id":25118968,"url":"https://github.com/dhonysilva/shire","last_synced_at":"2025-04-02T13:12:56.946Z","repository":{"id":274233491,"uuid":"922292292","full_name":"dhonysilva/shire","owner":"dhonysilva","description":"Application based on the Ash's official documentation for multi tenancy utilizing the :attribute strategy","archived":false,"fork":false,"pushed_at":"2025-02-02T14:36:21.000Z","size":89,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-02T15:28:36.670Z","etag":null,"topics":["ash","elixir","multitenancy","phoenix-framework"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dhonysilva.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2025-01-25T20:18:30.000Z","updated_at":"2025-02-02T12:55:22.000Z","dependencies_parsed_at":"2025-01-25T22:33:53.998Z","dependency_job_id":"a102192b-ace9-49ae-96d1-ecaa296be2e4","html_url":"https://github.com/dhonysilva/shire","commit_stats":null,"previous_names":["dhonysilva/shire"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhonysilva%2Fshire","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhonysilva%2Fshire/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhonysilva%2Fshire/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhonysilva%2Fshire/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dhonysilva","download_url":"https://codeload.github.com/dhonysilva/shire/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246819786,"owners_count":20839095,"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":["ash","elixir","multitenancy","phoenix-framework"],"created_at":"2025-02-08T04:23:36.093Z","updated_at":"2025-04-02T13:12:56.924Z","avatar_url":"https://github.com/dhonysilva.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Shire, multitenacy application with Ash Framework\n\nApplication created during the process of learning how to develop applications utilizing the multitenacy capability with Ash Framework.\n\n\nIn other project we studied the `:context` strategy, which uses postgres schemas where each tenant has its own database schema. On this strategy we can fetch data from each tenant by using the schema name as a prefix. For example, to fetch the tickets from tenant_01, tenant_02 e tenant_03 we can run the following query:\n\n```sql\nselect * from tenant_01.tickets;\nselect * from tenant_02.tickets;\nselect * from tenant_03.tickets;\n```\n\n## :attribute Multitenancy strategy\n\nFor this current project, we're utilizing the `:attribute` strategy described [here](https://hexdocs.pm/ash/multitenancy.html#attribute-multitenancy).\nThis strategy implies that each tenant has an attribute `organization_id` that is used to filter the data.\n\nThe Ticket resource thas the `organization_id` Foreign Key, which is used to determine which tenant it belongs to.\n\n```sql\nselect * from tickets;\n```\n\n| id         | subject                | status | organization_id |\n| ---------- | ---------------------- | ------ | --------------- |\n| 0d96594d   | Cable doesn't work     | open   | 9e1c0c0a        |\n| b6f8ffee   | Printer doesn't work   | open   | b948c8a3        |\n| 69e44730   | Mouse doesn't work     | open   | b948c8a3        |\n| 70c0d2ce   | Screen broken          | open   | 9e1c0c0a        |\n| c74350d5   | Slow Internet          | close  | 9e1c0c0a        |\n| 83b6c16d   | Fix the fan            | close  | 9e1c0c0a        |\n\nEach organization represents one tenant on the application. The organizations table has the following structure:\n\n```sql\nselect * from organizations;\n```\n\n| id         | name      | domain     |\n| ---------- | --------- | ---------- |\n| 9e1c0c0a   | Tenant 01 | tenant_01  |\n| b948c8a3   | Tenant 02 | tenant_02  |\n\n\n\n## Learn more\nI walked through the official Ash Multitenacy documentation while developing this project.\n\nhttps://hexdocs.pm/ash/multitenancy.html\n\n\nKamaro Lambert [(visit the project here)](https://github.com/kamaroly/helpdesk) has provided two outstanding tutorials on how to work with Multitenancy utilizing the `:context` strategy and I used all of the instrunctions he provided to build this project. Here are the links to the tutorials:\n\n- [Part 01](https://medium.com/@lambert.kamaro/how-to-build-a-saas-using-phoenix-and-ash-framework-1-4-69f3a622470d)\n- [Part 02](https://medium.com/@lambert.kamaro/how-to-build-a-saas-using-phoenix-and-ash-framework-2-4-41ccbb8003fe)\n\n### Setting up the project\nTo start this Phoenix server:\n\n  * Run `mix setup` to install and setup dependencies\n  * Run mix `mix ash.setup` to create the database and run the migrations.\n  * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`\n\nNow you can visit [`localhost:4000`](http://localhost:4000) from your browser.\n\nThe two routes available are:\n\n  * [`localhost:4000/tickets/`](http://localhost:4000/tickets/)\n\n  * [`localhost:4000/tickets/open`](http://localhost:4000/tickets/open)\n\n### Working with data\n\nOnce you open the application, you can create organizations and tickets.\n\nOn the IEx console, you can create organizations by running the following commands:\n\n```elixir\nShire.Accounts.create_organization(%{name: \"Tenant_01\", domain: \"tenant_01\"})\n```\n\nWe will receive the following response:\n\n```elixir\n{:ok,\n #Shire.Accounts.Organization\u003c\n   __meta__: #Ecto.Schema.Metadata\u003c:loaded, \"organizations\"\u003e,\n   id: \"912b94c9-2990-427a-824d-1d6fb9b956c3\",\n   name: \"Tenant_01\",\n   domain: \"tenant_01\",\n   went_live_at: nil,\n   email_domains: [],\n   inserted_at: ~U[2025-02-02 13:04:48.916403Z],\n   updated_at: ~U[2025-02-02 13:04:48.916403Z],\n   aggregates: %{},\n   calculations: %{},\n   ...\n \u003e}\n ```\n\nTo list all organizations, run the following command:\n\n```elixir\niex(2)\u003e Shire.Accounts.list_organizations()\n[debug] QUERY OK source=\"organizations\" db=1.5ms queue=3.4ms idle=258.4ms\nSELECT o0.\"id\", o0.\"name\", o0.\"domain\", o0.\"email_domains\", o0.\"inserted_at\", o0.\"updated_at\", o0.\"went_live_at\" FROM \"organizations\" AS o0 []\n↳ anonymous fn/3 in AshPostgres.DataLayer.run_query/2, at: lib/data_layer.ex:785\n{:ok,\n [\n   #Shire.Accounts.Organization\u003c\n     __meta__: #Ecto.Schema.Metadata\u003c:loaded, \"organizations\"\u003e,\n     id: \"912b94c9-2990-427a-824d-1d6fb9b956c3\",\n     name: \"Tenant_01\",\n     domain: \"tenant_01\",\n     went_live_at: nil,\n     email_domains: [],\n     inserted_at: ~U[2025-02-02 13:04:48.916403Z],\n     updated_at: ~U[2025-02-02 13:04:48.916403Z],\n     aggregates: %{},\n     calculations: %{},\n     ...\n   \u003e\n ]}\n```\n\nThere are two ways to create a ticket with the IEx console:\n\n\n```elixir\nShire.Support.Ticket\n|\u003e Ash.Changeset.for_create(:open, %{subject: \"Config the printer\"})\n|\u003e Ash.Changeset.set_tenant(\"9e1c0c0a-id-organization\")\n|\u003e Ash.create!()\n```\n\nOr\n```elixir\nShire.Support.open_ticket(%{subject: \"Broken cable\"}, tenant: \"9e1c0c0a-id-organization\")\n```\n\n### Fetching data\n\nSome ways to fetch data from Tickets:\n\n```elixir\nShire.Support.Ticket |\u003e Ash.Query.filter(contains(subject, \"5\")) |\u003e Ash.read!(tenant: \"6b44e248-2011-465c-b52e-bf94c7baa950\")\n```\n\nAnd also\n\n```elixir\nShire.Support.Ticket |\u003e Ash.Query.filter(status == :close and not(contains(subject, \"5\"))) |\u003e Ash.read!(tenant: \"6b44e248-2011-465c-b52e-bf94c7baa950\")\n```\n\n### Dealing with building artifacts problems\n\nSometimes there's some incompatibilies with the files on `_build` folder. In this case, proceed with one of the steps below.\n\nClear and recompile modules with:\n\n```bash\nmix compile --force\n```\n\nClear build artifacts and compile:\n\n```bash\nrm -rf _build\nmix deps.compile\nmix compile\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdhonysilva%2Fshire","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdhonysilva%2Fshire","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdhonysilva%2Fshire/lists"}