• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / realtime / edc4a63096e21d817cc1efd5872eb74205faa7df-PR-1379

27 May 2025 11:22AM UTC coverage: 83.357%. Remained the same
edc4a63096e21d817cc1efd5872eb74205faa7df-PR-1379

Pull #1379

github

filipecabaco
remove oriole from tests
Pull Request #1379: fix: execute migration to set index_bridging to disabled

1773 of 2127 relevant lines covered (83.36%)

2274.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.93
/lib/realtime_web/controllers/tenant_controller.ex
1
defmodule RealtimeWeb.TenantController do
2
  use RealtimeWeb, :controller
3
  use OpenApiSpex.ControllerSpecs
4

5
  require Logger
6
  import Realtime.Logs
7

8
  alias Realtime.Api
9
  alias Realtime.Api.Tenant
10
  alias Realtime.Database
11
  alias Realtime.PostgresCdc
12
  alias Realtime.Tenants
13
  alias Realtime.Tenants.Cache
14
  alias Realtime.Tenants.Migrations
15
  alias RealtimeWeb.OpenApiSchemas.EmptyResponse
16
  alias RealtimeWeb.OpenApiSchemas.ErrorResponse
17
  alias RealtimeWeb.OpenApiSchemas.NotFoundResponse
18
  alias RealtimeWeb.OpenApiSchemas.TenantHealthResponse
19
  alias RealtimeWeb.OpenApiSchemas.TenantParams
20
  alias RealtimeWeb.OpenApiSchemas.TenantResponse
21
  alias RealtimeWeb.OpenApiSchemas.TenantResponseList
22
  alias RealtimeWeb.OpenApiSchemas.UnauthorizedResponse
23

24
  @stop_timeout 10_000
25

26
  action_fallback(RealtimeWeb.FallbackController)
27

28
  operation(:index,
1✔
29
    summary: "List tenants",
30
    parameters: [
31
      authorization: [
32
        in: :header,
33
        name: "Authorization",
34
        schema: %OpenApiSpex.Schema{type: :string},
35
        required: true,
36
        example:
37
          "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY"
38
      ]
39
    ],
40
    responses: %{
41
      200 => TenantResponseList.response(),
42
      403 => EmptyResponse.response()
43
    }
44
  )
45

46
  def index(conn, _params) do
47
    tenants = Api.list_tenants()
1✔
48
    render(conn, "index.json", tenants: tenants)
1✔
49
  end
50

51
  operation(:show,
1✔
52
    summary: "Fetch tenant",
53
    parameters: [
54
      token: [
55
        in: :header,
56
        name: "Authorization",
57
        schema: %OpenApiSpex.Schema{type: :string},
58
        required: true,
59
        example:
60
          "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY"
61
      ],
62
      tenant_id: [in: :path, description: "Tenant ID", type: :string]
63
    ],
64
    responses: %{
65
      200 => TenantResponse.response(),
66
      403 => EmptyResponse.response(),
67
      404 => NotFoundResponse.response()
68
    }
69
  )
70

71
  def show(conn, %{"tenant_id" => id}) do
72
    Logger.metadata(external_id: id, project: id)
4✔
73

74
    tenant = Api.get_tenant_by_external_id(id)
4✔
75

76
    case tenant do
4✔
77
      %Tenant{} = tenant ->
78
        render(conn, "show.json", tenant: tenant)
3✔
79

80
      nil ->
81
        conn
82
        |> put_status(404)
83
        |> render("not_found.json", tenant: nil)
1✔
84
    end
85
  end
86

87
  operation(:create,
1✔
88
    summary: "Create or update tenant",
89
    parameters: [
90
      token: [
91
        in: :header,
92
        name: "Authorization",
93
        schema: %OpenApiSpex.Schema{type: :string},
94
        required: true,
95
        example:
96
          "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY"
97
      ]
98
    ],
99
    request_body: TenantParams.params(),
100
    responses: %{
101
      200 => TenantResponse.response(),
102
      403 => EmptyResponse.response()
103
    }
104
  )
105

106
  @spec create(any(), map()) :: any()
107
  def create(conn, %{"tenant" => params}) do
108
    external_id = Map.get(params, "external_id")
3✔
109

110
    case Tenant.changeset(%Tenant{}, params) do
3✔
111
      %{valid?: true} -> update(conn, %{"tenant_id" => external_id, "tenant" => params})
2✔
112
      changeset -> changeset
1✔
113
    end
114
  end
115

116
  operation(:update,
2✔
117
    summary: "Create or update tenant",
118
    parameters: [
119
      token: [
120
        in: :header,
121
        name: "Authorization",
122
        schema: %OpenApiSpex.Schema{type: :string},
123
        required: true,
124
        example:
125
          "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY"
126
      ],
127
      tenant_id: [in: :path, description: "Tenant ID", type: :string]
128
    ],
129
    request_body: TenantParams.params(),
130
    responses: %{
131
      200 => TenantResponse.response(),
132
      403 => EmptyResponse.response()
133
    }
134
  )
135

136
  def update(conn, %{"tenant_id" => external_id, "tenant" => tenant_params}) do
137
    Logger.metadata(external_id: external_id, project: external_id)
5✔
138
    tenant = Api.get_tenant_by_external_id(external_id)
5✔
139

140
    case tenant do
5✔
141
      nil ->
142
        tenant_params = tenant_params |> Map.put("external_id", external_id) |> Map.put("name", external_id)
3✔
143

144
        extensions =
3✔
145
          Enum.reduce(tenant_params["extensions"], [], fn
146
            %{"type" => type, "settings" => settings}, acc -> [%{"type" => type, "settings" => settings} | acc]
2✔
147
            _e, acc -> acc
×
148
          end)
149

150
        with {:ok, %Tenant{} = tenant} <- Api.create_tenant(%{tenant_params | "extensions" => extensions}),
3✔
151
             res when res in [:ok, :noop] <- Migrations.run_migrations(tenant) do
2✔
152
          Logger.metadata(external_id: tenant.external_id, project: tenant.external_id)
2✔
153

154
          conn
155
          |> put_status(:created)
156
          |> put_resp_header("location", Routes.tenant_path(conn, :show, tenant))
157
          |> render("show.json", tenant: tenant)
2✔
158
        end
159

160
      tenant ->
161
        with {:ok, %Tenant{} = tenant} <- Api.update_tenant(tenant, tenant_params) do
2✔
162
          conn
163
          |> put_status(:ok)
164
          |> put_resp_header("location", Routes.tenant_path(conn, :show, tenant))
165
          |> render("show.json", tenant: tenant)
2✔
166
        end
167
    end
168
  end
169

170
  operation(:delete,
1✔
171
    summary: "Delete tenant",
172
    parameters: [
173
      token: [
174
        in: :header,
175
        name: "Authorization",
176
        schema: %OpenApiSpex.Schema{type: :string},
177
        required: true,
178
        example:
179
          "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY"
180
      ],
181
      tenant_id: [in: :path, description: "Tenant ID", type: :string]
182
    ],
183
    responses: %{
184
      204 => EmptyResponse.response(),
185
      403 => UnauthorizedResponse.response(),
186
      500 => ErrorResponse.response()
187
    }
188
  )
189

190
  def delete(conn, %{"tenant_id" => tenant_id}) do
191
    Logger.metadata(external_id: tenant_id, project: tenant_id)
1✔
192

193
    stop_all_timeout = Enum.count(PostgresCdc.available_drivers()) * 1_000
1✔
194

195
    with %Tenant{} = tenant <- Api.get_tenant_by_external_id(tenant_id, :primary),
1✔
196
         _ <- Tenants.suspend_tenant_by_external_id(tenant_id),
×
197
         true <- Api.delete_tenant_by_external_id(tenant_id),
×
198
         :ok <- Cache.distributed_invalidate_tenant_cache(tenant_id),
×
199
         :ok <- PostgresCdc.stop_all(tenant, stop_all_timeout),
×
200
         :ok <- Database.replication_slot_teardown(tenant) do
×
201
      send_resp(conn, 204, "")
×
202
    else
203
      nil ->
204
        log_error("TenantNotFound", "Tenant not found")
1✔
205
        send_resp(conn, 204, "")
1✔
206

207
      err ->
208
        log_error("UnableToDeleteTenant", err)
×
209
        conn |> put_status(500) |> json(err) |> halt()
×
210
    end
211
  end
212

213
  operation(:reload,
1✔
214
    summary: "Reload tenant",
215
    parameters: [
216
      token: [
217
        in: :header,
218
        name: "Authorization",
219
        schema: %OpenApiSpex.Schema{type: :string},
220
        required: true,
221
        example:
222
          "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY"
223
      ],
224
      tenant_id: [in: :path, description: "Tenant ID", type: :string]
225
    ],
226
    responses: %{
227
      204 => EmptyResponse.response(),
228
      403 => EmptyResponse.response(),
229
      404 => NotFoundResponse.response()
230
    }
231
  )
232

233
  def reload(conn, %{"tenant_id" => tenant_id}) do
234
    Logger.metadata(external_id: tenant_id, project: tenant_id)
2✔
235

236
    case Tenants.get_tenant_by_external_id(tenant_id) do
2✔
237
      nil ->
238
        log_error("TenantNotFound", "Tenant not found")
1✔
239

240
        conn
241
        |> put_status(404)
242
        |> render("not_found.json", tenant: nil)
1✔
243

244
      tenant ->
245
        PostgresCdc.stop_all(tenant, @stop_timeout)
1✔
246
        send_resp(conn, 204, "")
1✔
247
    end
248
  end
249

250
  operation(:health,
1✔
251
    summary: "Tenant health",
252
    parameters: [
253
      token: [
254
        in: :header,
255
        name: "Authorization",
256
        schema: %OpenApiSpex.Schema{type: :string},
257
        required: true,
258
        example:
259
          "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODAxNjIxNTR9.U9orU6YYqXAtpF8uAiw6MS553tm4XxRzxOhz2IwDhpY"
260
      ],
261
      tenant_id: [in: :path, description: "Tenant ID", type: :string]
262
    ],
263
    responses: %{
264
      200 => TenantHealthResponse.response(),
265
      403 => EmptyResponse.response(),
266
      404 => NotFoundResponse.response()
267
    }
268
  )
269

270
  def health(conn, %{"tenant_id" => tenant_id}) do
271
    Logger.metadata(external_id: tenant_id, project: tenant_id)
5✔
272

273
    case Tenants.health_check(tenant_id) do
5✔
274
      {:ok, response} ->
275
        json(conn, %{data: response})
3✔
276

277
      {:error, %{healthy: false} = response} ->
278
        json(conn, %{data: response})
1✔
279

280
      {:error, :tenant_not_found} ->
281
        log_error("TenantNotFound", "Tenant not found")
1✔
282

283
        conn
284
        |> put_status(404)
285
        |> render("not_found.json", tenant: nil)
1✔
286
    end
287
  end
288
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc