towerops/lib/towerops/organizations/subscription_limits.ex
2026-02-05 15:04:34 -06:00

142 lines
3.6 KiB
Elixir

defmodule Towerops.Organizations.SubscriptionLimits do
@moduledoc """
Subscription plan limits and quota enforcement.
Currently supports:
- Free plan: 10 devices max, 1 owned organization per user
- Future plans can be added here (per-device pricing, etc.)
"""
import Ecto.Query
alias Towerops.Devices.Device
alias Towerops.Organizations.Membership
alias Towerops.Organizations.Organization
alias Towerops.Repo
@free_device_limit 10
@doc """
Returns the device limit for a given subscription plan.
## Examples
iex> device_limit("free")
10
iex> device_limit("paid")
:unlimited
"""
def device_limit("free"), do: @free_device_limit
def device_limit(_other), do: :unlimited
@doc """
Checks if an organization is within its device quota.
Returns `{:ok, :within_limit}` if the organization can add more devices.
Returns `{:error, :at_limit, current, max}` if at or over the limit.
## Examples
iex> check_device_limit(%Organization{subscription_plan: "free"})
{:ok, :within_limit}
iex> check_device_limit(%Organization{subscription_plan: "free"})
{:error, :at_limit, 10, 10}
"""
def check_device_limit(%Organization{} = organization) do
case device_limit(organization.subscription_plan) do
:unlimited ->
{:ok, :within_limit}
max_devices ->
current = count_organization_devices(organization.id)
if current >= max_devices do
{:error, :at_limit, current, max_devices}
else
{:ok, :within_limit}
end
end
end
@doc """
Returns the current device count and limit for an organization.
Returns `{current_count, limit}` where limit is either an integer or `:unlimited`.
## Examples
iex> device_quota(%Organization{id: "...", subscription_plan: "free"})
{5, 10}
iex> device_quota(%Organization{id: "...", subscription_plan: "paid"})
{45, :unlimited}
"""
def device_quota(%Organization{} = organization) do
current = count_organization_devices(organization.id)
limit = device_limit(organization.subscription_plan)
{current, limit}
end
@doc """
Checks if a user can create a free organization.
Users are limited to owning 1 free organization. They can create unlimited
paid organizations or be invited as members to unlimited organizations.
Returns `true` if the user can create another free organization, `false` otherwise.
## Examples
iex> can_create_free_organization?(user_id)
true
iex> can_create_free_organization?(user_id)
false
"""
def can_create_free_organization?(user_id) do
count_user_owned_free_organizations(user_id) < 1
end
@doc """
Counts how many free organizations a user owns.
Only counts organizations where the user has the `:owner` role.
Memberships as admin/member/viewer do not count.
## Examples
iex> count_user_owned_free_organizations(user_id)
1
iex> count_user_owned_free_organizations(user_id)
0
"""
def count_user_owned_free_organizations(user_id) do
Repo.aggregate(
from(o in Organization,
join: m in Membership,
on: m.organization_id == o.id,
where: m.user_id == ^user_id,
where: m.role == :owner,
where: o.subscription_plan == "free"
),
:count
)
end
@doc """
Counts total devices for an organization.
Counts all devices belonging to the organization, whether assigned to a site or not.
"""
def count_organization_devices(organization_id) do
Repo.aggregate(
from(d in Device,
where: d.organization_id == ^organization_id
),
:count
)
end
end