Skip to content

Commit d57279d

Browse files
authored
feat: entitlements (supabase#39338)
* feat: entitlements * Sample usage * format * Reworked * format * gen * removed * removed
1 parent 46b4a70 commit d57279d

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query'
2+
import { get, handleError } from 'data/fetchers'
3+
import { ResponseError } from 'types/base'
4+
import type { components } from 'api-types'
5+
6+
export type EntitlementsVariables = {
7+
slug: string
8+
}
9+
10+
export type EntitlementConfig =
11+
components['schemas']['ListEntitlementsResponse']['entitlements'][0]['config']
12+
export type Entitlement = components['schemas']['ListEntitlementsResponse']['entitlements'][0]
13+
14+
export async function getEntitlements({ slug }: EntitlementsVariables, signal?: AbortSignal) {
15+
if (!slug) throw new Error('slug is required')
16+
17+
const { data, error } = await get('/platform/organizations/{slug}/entitlements', {
18+
params: { path: { slug } },
19+
signal,
20+
})
21+
if (error) handleError(error)
22+
23+
return data
24+
}
25+
26+
export type EntitlementsData = Awaited<ReturnType<typeof getEntitlements>>
27+
export type EntitlementsError = ResponseError
28+
29+
export const useEntitlementsQuery = <TData = EntitlementsData>(
30+
{ slug }: EntitlementsVariables,
31+
{ enabled = true, ...options }: UseQueryOptions<EntitlementsData, EntitlementsError, TData> = {}
32+
) => {
33+
return useQuery<EntitlementsData, EntitlementsError, TData>(
34+
['entitlements', slug],
35+
({ signal }) => getEntitlements({ slug }, signal),
36+
{ enabled: enabled && typeof slug !== 'undefined', ...options, staleTime: 1 * 60 * 1000 }
37+
)
38+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { useEntitlementsQuery } from 'data/entitlements/entitlements-query'
2+
import { useMemo } from 'react'
3+
import { useSelectedOrganizationQuery } from './useSelectedOrganization'
4+
import type { EntitlementConfig } from 'data/entitlements/entitlements-query'
5+
6+
export function useCheckEntitlements(
7+
featureKey: string,
8+
organizationSlug?: string,
9+
options?: {
10+
enabled?: boolean
11+
}
12+
) {
13+
// If no organizationSlug provided, try to get it from the selected organization
14+
const shouldGetSelectedOrg = !organizationSlug && options?.enabled !== false
15+
const {
16+
data: selectedOrg,
17+
isLoading: isLoadingSelectedOrg,
18+
isSuccess: isSuccessSelectedOrg,
19+
} = useSelectedOrganizationQuery({
20+
enabled: shouldGetSelectedOrg,
21+
})
22+
23+
const finalOrgSlug = organizationSlug || selectedOrg?.slug
24+
const enabled = options?.enabled !== false && !!finalOrgSlug
25+
26+
const {
27+
data: entitlementsData,
28+
isLoading: isLoadingEntitlements,
29+
isSuccess: isSuccessEntitlements,
30+
} = useEntitlementsQuery({ slug: finalOrgSlug! }, { enabled })
31+
32+
const { hasAccess, entitlementConfig } = useMemo((): {
33+
hasAccess: boolean
34+
entitlementConfig: EntitlementConfig
35+
} => {
36+
// If no organization slug, no access
37+
if (!finalOrgSlug) return { hasAccess: false, entitlementConfig: { enabled: false } }
38+
39+
const entitlement = entitlementsData?.entitlements.find(
40+
(entitlement) => entitlement.feature.key === featureKey
41+
)
42+
const entitlementConfig = entitlement?.config ?? { enabled: false }
43+
44+
if (!entitlement) return { hasAccess: false, entitlementConfig: { enabled: false } }
45+
46+
return { hasAccess: entitlement.hasAccess, entitlementConfig }
47+
}, [entitlementsData, featureKey, finalOrgSlug])
48+
49+
const isLoading = shouldGetSelectedOrg ? isLoadingSelectedOrg : isLoadingEntitlements
50+
const isSuccess = shouldGetSelectedOrg
51+
? isSuccessSelectedOrg && isSuccessEntitlements
52+
: isSuccessEntitlements
53+
54+
return { hasAccess, entitlementConfig, isLoading, isSuccess }
55+
}

packages/api-types/types/platform.d.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,6 +1251,46 @@ export interface paths {
12511251
patch?: never
12521252
trace?: never
12531253
}
1254+
'/platform/organizations/{slug}/entitlements': {
1255+
parameters: {
1256+
query?: never
1257+
header?: never
1258+
path?: never
1259+
cookie?: never
1260+
}
1261+
/**
1262+
* Get entitlements for an organization
1263+
* @description Returns the entitlements available to the organization based on their plan and any overrides.
1264+
*/
1265+
get: operations['OrganizationEntitlementsController_getEntitlements']
1266+
put?: never
1267+
post?: never
1268+
delete?: never
1269+
options?: never
1270+
head?: never
1271+
patch?: never
1272+
trace?: never
1273+
}
1274+
'/platform/organizations/{slug}/entitlements/entitlements': {
1275+
parameters: {
1276+
query?: never
1277+
header?: never
1278+
path?: never
1279+
cookie?: never
1280+
}
1281+
/**
1282+
* Get entitlements for an organization
1283+
* @description Returns the entitlements available to the organization based on their plan and any overrides.
1284+
*/
1285+
get: operations['OrganizationEntitlementsController_getEntitlements']
1286+
put?: never
1287+
post?: never
1288+
delete?: never
1289+
options?: never
1290+
head?: never
1291+
patch?: never
1292+
trace?: never
1293+
}
12541294
'/platform/organizations/{slug}/members': {
12551295
parameters: {
12561296
query?: never
@@ -5731,6 +5771,11 @@ export interface components {
57315771
billing_name?: string
57325772
billing_via_partner: boolean
57335773
email: string
5774+
tax_id: {
5775+
country: string
5776+
type: string
5777+
value: string
5778+
} | null
57345779
}
57355780
DatabaseDetailResponse: {
57365781
/** @enum {string} */
@@ -6942,6 +6987,29 @@ export interface components {
69426987
LinkClazarBuyerBody: {
69436988
buyer_id: string
69446989
}
6990+
ListEntitlementsResponse: {
6991+
entitlements: {
6992+
config:
6993+
| {
6994+
enabled: boolean
6995+
}
6996+
| {
6997+
enabled: boolean
6998+
unlimited: boolean
6999+
value: number
7000+
}
7001+
| {
7002+
enabled: boolean
7003+
set: string[]
7004+
}
7005+
feature: {
7006+
key: string
7007+
/** @enum {string} */
7008+
type: 'boolean' | 'numeric' | 'set'
7009+
}
7010+
hasAccess: boolean
7011+
}[]
7012+
}
69457013
ListGitHubConnectionsResponse: {
69467014
connections: {
69477015
branch_limit: number
@@ -14178,6 +14246,92 @@ export interface operations {
1417814246
}
1417914247
}
1418014248
}
14249+
OrganizationEntitlementsController_getEntitlements: {
14250+
parameters: {
14251+
query?: never
14252+
header?: never
14253+
path: {
14254+
/** @description Organization slug */
14255+
slug: string
14256+
}
14257+
cookie?: never
14258+
}
14259+
requestBody?: never
14260+
responses: {
14261+
200: {
14262+
headers: {
14263+
[name: string]: unknown
14264+
}
14265+
content: {
14266+
'application/json': components['schemas']['ListEntitlementsResponse']
14267+
}
14268+
}
14269+
/** @description Unauthorized */
14270+
401: {
14271+
headers: {
14272+
[name: string]: unknown
14273+
}
14274+
content?: never
14275+
}
14276+
/** @description Forbidden action */
14277+
403: {
14278+
headers: {
14279+
[name: string]: unknown
14280+
}
14281+
content?: never
14282+
}
14283+
/** @description Rate limit exceeded */
14284+
429: {
14285+
headers: {
14286+
[name: string]: unknown
14287+
}
14288+
content?: never
14289+
}
14290+
}
14291+
}
14292+
OrganizationEntitlementsController_getEntitlements: {
14293+
parameters: {
14294+
query?: never
14295+
header?: never
14296+
path: {
14297+
/** @description Organization slug */
14298+
slug: string
14299+
}
14300+
cookie?: never
14301+
}
14302+
requestBody?: never
14303+
responses: {
14304+
200: {
14305+
headers: {
14306+
[name: string]: unknown
14307+
}
14308+
content: {
14309+
'application/json': components['schemas']['ListEntitlementsResponse']
14310+
}
14311+
}
14312+
/** @description Unauthorized */
14313+
401: {
14314+
headers: {
14315+
[name: string]: unknown
14316+
}
14317+
content?: never
14318+
}
14319+
/** @description Forbidden action */
14320+
403: {
14321+
headers: {
14322+
[name: string]: unknown
14323+
}
14324+
content?: never
14325+
}
14326+
/** @description Rate limit exceeded */
14327+
429: {
14328+
headers: {
14329+
[name: string]: unknown
14330+
}
14331+
content?: never
14332+
}
14333+
}
14334+
}
1418114335
MembersController_getMembers: {
1418214336
parameters: {
1418314337
query?: never

0 commit comments

Comments
 (0)