diff --git a/apps/web/app/api/billing/webhook/route.ts b/apps/web/app/api/billing/webhook/route.ts index a13bbcb6e..38a79abd9 100644 --- a/apps/web/app/api/billing/webhook/route.ts +++ b/apps/web/app/api/billing/webhook/route.ts @@ -8,29 +8,31 @@ import billingConfig from '~/config/billing.config'; * @description Handle the webhooks from Stripe related to checkouts */ export async function POST(request: Request) { - // we can infer the provider from the billing config or the request - // for simplicity, we'll use the billing config for now - // TODO: use dynamic provider from request? const provider = billingConfig.provider; Logger.info( { - name: 'billing', + name: 'billing.webhook', provider, }, `Received billing webhook. Processing...`, ); - const clientProvider = () => getSupabaseRouteHandlerClient({ admin: true }); + const supabaseClientProvider = () => + getSupabaseRouteHandlerClient({ admin: true }); - const service = await getBillingEventHandlerService(clientProvider, provider); + const service = await getBillingEventHandlerService( + supabaseClientProvider, + provider, + billingConfig, + ); try { await service.handleWebhookEvent(request); Logger.info( { - name: 'billing', + name: 'billing.webhook', }, `Successfully processed billing webhook`, ); diff --git a/apps/web/app/api/db/webhook/route.ts b/apps/web/app/api/db/webhook/route.ts index b5ae3145e..21967e4cf 100644 --- a/apps/web/app/api/db/webhook/route.ts +++ b/apps/web/app/api/db/webhook/route.ts @@ -11,10 +11,21 @@ const webhooksSecret = z const service = new DatabaseWebhookHandlerService(); -export async function POST(request: Request) { - await service.handleWebhook(request, webhooksSecret); +const response = (status: number) => new Response(null, { status }); - return new Response(null, { - status: 200, - }); +/** + * @name POST + * @description POST handler for the webhook route that handles the webhook event + * @param request + * @constructor + */ +export async function POST(request: Request) { + try { + // handle the webhook event + await service.handleWebhook(request, webhooksSecret); + + return response(200); + } catch { + return response(500); + } } diff --git a/packages/billing/core/src/create-billing-schema.ts b/packages/billing/core/src/create-billing-schema.ts index f085dddde..d455f6d5a 100644 --- a/packages/billing/core/src/create-billing-schema.ts +++ b/packages/billing/core/src/create-billing-schema.ts @@ -103,6 +103,34 @@ export const PlanSchema = z message: 'Line item IDs must be unique', path: ['lineItems'], }, + ) + .refine( + (data) => { + if (data.paymentType === 'one-time') { + const meteredItems = data.lineItems.filter( + (item) => item.type === 'metered', + ); + + return meteredItems.length === 0; + } + }, + { + message: 'One-time plans must not have metered line items', + path: ['paymentType', 'lineItems'], + }, + ) + .refine( + (data) => { + if (data.paymentType === 'one-time') { + const baseItems = data.lineItems.filter((item) => item.type !== 'base'); + + return baseItems.length === 0; + } + }, + { + message: 'One-time plans must not have non-base line items', + path: ['paymentType', 'lineItems'], + }, ); const ProductSchema = z @@ -259,3 +287,20 @@ export function getProductPlanPairByVariantId( throw new Error('Plan not found'); } + +export function getLineItemTypeById( + config: z.infer, + id: string, +) { + for (const product of config.products) { + for (const plan of product.plans) { + for (const lineItem of plan.lineItems) { + if (lineItem.type === id) { + return lineItem.type; + } + } + } + } + + throw new Error(`Line Item with ID ${id} not found`); +} diff --git a/packages/billing/core/src/schema/index.ts b/packages/billing/core/src/schema/index.ts index 3b284a09b..71a231be5 100644 --- a/packages/billing/core/src/schema/index.ts +++ b/packages/billing/core/src/schema/index.ts @@ -3,3 +3,4 @@ export * from './create-biling-portal-session.schema'; export * from './retrieve-checkout-session.schema'; export * from './cancel-subscription-params.schema'; export * from './report-billing-usage.schema'; +export * from './update-subscription-params.schema'; diff --git a/packages/billing/core/src/schema/update-subscription-params.schema.ts b/packages/billing/core/src/schema/update-subscription-params.schema.ts new file mode 100644 index 000000000..ac3844420 --- /dev/null +++ b/packages/billing/core/src/schema/update-subscription-params.schema.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +export const UpdateSubscriptionParamsSchema = z.object({ + subscriptionId: z.string().min(1), + subscriptionItemId: z.string().min(1), + quantity: z.number().min(1), +}); diff --git a/packages/billing/core/src/services/billing-strategy-provider.service.ts b/packages/billing/core/src/services/billing-strategy-provider.service.ts index 4e02b4411..b86a4ee59 100644 --- a/packages/billing/core/src/services/billing-strategy-provider.service.ts +++ b/packages/billing/core/src/services/billing-strategy-provider.service.ts @@ -4,9 +4,10 @@ import { CancelSubscriptionParamsSchema, CreateBillingCheckoutSchema, CreateBillingPortalSessionSchema, + ReportBillingUsageSchema, RetrieveCheckoutSessionSchema, + UpdateSubscriptionParamsSchema, } from '../schema'; -import { ReportBillingUsageSchema } from '../schema'; export abstract class BillingStrategyProviderService { abstract createBillingPortalSession( @@ -44,4 +45,10 @@ export abstract class BillingStrategyProviderService { ): Promise<{ success: boolean; }>; + + abstract updateSubscription( + params: z.infer, + ): Promise<{ + success: boolean; + }>; } diff --git a/packages/billing/gateway/src/server/services/billing-event-handler/billing-event-handler.service.ts b/packages/billing/gateway/src/server/services/billing-event-handler/billing-event-handler.service.ts index 8a9ca667b..1aad51da2 100644 --- a/packages/billing/gateway/src/server/services/billing-event-handler/billing-event-handler.service.ts +++ b/packages/billing/gateway/src/server/services/billing-event-handler/billing-event-handler.service.ts @@ -154,7 +154,7 @@ export class BillingEventHandlerService { Logger.info( { - namespace: 'billing', + namespace: this.namespace, sessionId, }, 'Successfully updated payment status', diff --git a/packages/billing/gateway/src/server/services/billing-event-handler/billing-gateway-factory.service.ts b/packages/billing/gateway/src/server/services/billing-event-handler/billing-gateway-factory.service.ts index ae9a9fb58..16b919034 100644 --- a/packages/billing/gateway/src/server/services/billing-event-handler/billing-gateway-factory.service.ts +++ b/packages/billing/gateway/src/server/services/billing-event-handler/billing-gateway-factory.service.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import { + BillingConfig, BillingProviderSchema, BillingWebhookHandlerService, } from '@kit/billing'; @@ -8,12 +9,13 @@ import { export class BillingEventHandlerFactoryService { static async GetProviderStrategy( provider: z.infer, + config: BillingConfig, ): Promise { switch (provider) { case 'stripe': { const { StripeWebhookHandlerService } = await import('@kit/stripe'); - return new StripeWebhookHandlerService(); + return new StripeWebhookHandlerService(config); } case 'lemon-squeezy': { @@ -21,7 +23,7 @@ export class BillingEventHandlerFactoryService { '@kit/lemon-squeezy' ); - return new LemonSqueezyWebhookHandlerService(); + return new LemonSqueezyWebhookHandlerService(config); } case 'paddle': { diff --git a/packages/billing/gateway/src/server/services/billing-event-handler/billing-gateway-provider-factory.ts b/packages/billing/gateway/src/server/services/billing-event-handler/billing-gateway-provider-factory.ts index 917567930..e8244738d 100644 --- a/packages/billing/gateway/src/server/services/billing-event-handler/billing-gateway-provider-factory.ts +++ b/packages/billing/gateway/src/server/services/billing-event-handler/billing-gateway-provider-factory.ts @@ -1,3 +1,4 @@ +import { BillingConfig } from '@kit/billing'; import { Database } from '@kit/supabase/database'; import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client'; @@ -12,9 +13,12 @@ import { BillingEventHandlerFactoryService } from './billing-gateway-factory.ser export async function getBillingEventHandlerService( clientProvider: () => ReturnType, provider: Database['public']['Enums']['billing_provider'], + config: BillingConfig, ) { - const strategy = - await BillingEventHandlerFactoryService.GetProviderStrategy(provider); + const strategy = await BillingEventHandlerFactoryService.GetProviderStrategy( + provider, + config, + ); return new BillingEventHandlerService(clientProvider, strategy); } diff --git a/packages/billing/gateway/src/server/services/billing-gateway/billing-gateway.service.ts b/packages/billing/gateway/src/server/services/billing-gateway/billing-gateway.service.ts index a7e7da0ed..2d349e9e8 100644 --- a/packages/billing/gateway/src/server/services/billing-gateway/billing-gateway.service.ts +++ b/packages/billing/gateway/src/server/services/billing-gateway/billing-gateway.service.ts @@ -5,7 +5,9 @@ import { CancelSubscriptionParamsSchema, CreateBillingCheckoutSchema, CreateBillingPortalSessionSchema, + ReportBillingUsageSchema, RetrieveCheckoutSessionSchema, + UpdateSubscriptionParamsSchema, } from '@kit/billing/schema'; import { BillingGatewayFactoryService } from './billing-gateway-factory.service'; @@ -92,4 +94,35 @@ export class BillingGatewayService { return strategy.cancelSubscription(payload); } + + /** + * Reports the usage of the billing. + * @description This is used to report the usage of the billing to the provider. + * @param params + */ + async reportUsage(params: z.infer) { + const strategy = await BillingGatewayFactoryService.GetProviderStrategy( + this.provider, + ); + + const payload = ReportBillingUsageSchema.parse(params); + + return strategy.reportUsage(payload); + } + + /** + * Updates a subscription with the specified parameters. + * @param params + */ + async updateSubscriptionItem( + params: z.infer, + ) { + const strategy = await BillingGatewayFactoryService.GetProviderStrategy( + this.provider, + ); + + const payload = UpdateSubscriptionParamsSchema.parse(params); + + return strategy.updateSubscription(payload); + } } diff --git a/packages/billing/lemon-squeezy/src/services/lemon-squeezy-billing-strategy.service.ts b/packages/billing/lemon-squeezy/src/services/lemon-squeezy-billing-strategy.service.ts index 8172e358b..95a98e939 100644 --- a/packages/billing/lemon-squeezy/src/services/lemon-squeezy-billing-strategy.service.ts +++ b/packages/billing/lemon-squeezy/src/services/lemon-squeezy-billing-strategy.service.ts @@ -2,6 +2,7 @@ import { cancelSubscription, createUsageRecord, getCheckout, + updateSubscriptionItem, } from '@lemonsqueezy/lemonsqueezy.js'; import 'server-only'; import { z } from 'zod'; @@ -13,6 +14,7 @@ import { CreateBillingPortalSessionSchema, ReportBillingUsageSchema, RetrieveCheckoutSessionSchema, + UpdateSubscriptionParamsSchema, } from '@kit/billing/schema'; import { Logger } from '@kit/shared/logger'; @@ -240,4 +242,35 @@ export class LemonSqueezyBillingStrategyService return { success: true }; } + + async updateSubscription( + params: z.infer, + ) { + const ctx = { + name: 'billing.lemon-squeezy', + ...params, + }; + + Logger.info(ctx, 'Updating subscription...'); + + const { error } = await updateSubscriptionItem(params.subscriptionItemId, { + quantity: params.quantity, + }); + + if (error) { + Logger.error( + { + ...ctx, + error, + }, + 'Failed to update subscription', + ); + + throw error; + } + + Logger.info(ctx, 'Subscription updated successfully'); + + return { success: true }; + } } diff --git a/packages/billing/lemon-squeezy/src/services/lemon-squeezy-webhook-handler.service.ts b/packages/billing/lemon-squeezy/src/services/lemon-squeezy-webhook-handler.service.ts index 8739195a9..addf7b8d5 100644 --- a/packages/billing/lemon-squeezy/src/services/lemon-squeezy-webhook-handler.service.ts +++ b/packages/billing/lemon-squeezy/src/services/lemon-squeezy-webhook-handler.service.ts @@ -1,7 +1,11 @@ import { getOrder, getVariant } from '@lemonsqueezy/lemonsqueezy.js'; import { createHmac, timingSafeEqual } from 'crypto'; -import { BillingWebhookHandlerService } from '@kit/billing'; +import { + BillingConfig, + BillingWebhookHandlerService, + getLineItemTypeById, +} from '@kit/billing'; import { Logger } from '@kit/shared/logger'; import { Database } from '@kit/supabase/database'; @@ -35,6 +39,8 @@ export class LemonSqueezyWebhookHandlerService private readonly namespace = 'billing.lemon-squeezy'; + constructor(private readonly config: BillingConfig) {} + /** * @description Verifies the webhook signature - should throw an error if the signature is invalid */ @@ -307,6 +313,7 @@ export class LemonSqueezyWebhookHandlerService product_id: item.product, variant_id: item.variant, price_amount: item.unitAmount, + type: getLineItemTypeById(this.config, item.id), }; }); diff --git a/packages/billing/stripe/src/services/stripe-billing-strategy.service.ts b/packages/billing/stripe/src/services/stripe-billing-strategy.service.ts index e69bd29ad..1e49ff72f 100644 --- a/packages/billing/stripe/src/services/stripe-billing-strategy.service.ts +++ b/packages/billing/stripe/src/services/stripe-billing-strategy.service.ts @@ -9,6 +9,7 @@ import { CreateBillingPortalSessionSchema, ReportBillingUsageSchema, RetrieveCheckoutSessionSchema, + UpdateSubscriptionParamsSchema, } from '@kit/billing/schema'; import { Logger } from '@kit/shared/logger'; @@ -198,6 +199,52 @@ export class StripeBillingStrategyService return { success: true }; } + async updateSubscription( + params: z.infer, + ) { + const stripe = await this.stripeProvider(); + + Logger.info( + { + name: 'billing.stripe', + ...params, + }, + 'Updating subscription...', + ); + + try { + await stripe.subscriptions.update(params.subscriptionId, { + items: [ + { + id: params.subscriptionItemId, + quantity: params.quantity, + }, + ], + }); + + Logger.info( + { + name: 'billing.stripe', + ...params, + }, + 'Subscription updated successfully', + ); + + return { success: true }; + } catch (e) { + Logger.error( + { + name: 'billing.stripe', + ...params, + error: e, + }, + 'Failed to update subscription', + ); + + throw new Error('Failed to update subscription'); + } + } + private async stripeProvider(): Promise { return createStripeClient(); } diff --git a/packages/billing/stripe/src/services/stripe-webhook-handler.service.ts b/packages/billing/stripe/src/services/stripe-webhook-handler.service.ts index dd666d645..2e827c8ac 100644 --- a/packages/billing/stripe/src/services/stripe-webhook-handler.service.ts +++ b/packages/billing/stripe/src/services/stripe-webhook-handler.service.ts @@ -1,6 +1,10 @@ import Stripe from 'stripe'; -import { BillingWebhookHandlerService } from '@kit/billing'; +import { + BillingConfig, + BillingWebhookHandlerService, + getLineItemTypeById, +} from '@kit/billing'; import { Logger } from '@kit/shared/logger'; import { Database } from '@kit/supabase/database'; @@ -18,6 +22,8 @@ export class StripeWebhookHandlerService { private stripe: Stripe | undefined; + constructor(private readonly config: BillingConfig) {} + private readonly provider: Database['public']['Enums']['billing_provider'] = 'stripe'; @@ -134,6 +140,8 @@ export class StripeWebhookHandlerService const accountId = session.client_reference_id!; const customerId = session.customer as string; + // if it's a subscription, we need to retrieve the subscription + // and build the payload for the subscription if (isSubscription) { const subscriptionId = session.subscription as string; const subscription = await stripe.subscriptions.retrieve(subscriptionId); @@ -154,8 +162,10 @@ export class StripeWebhookHandlerService return onCheckoutCompletedCallback(payload); } else { + // if it's a one-time payment, we need to retrieve the session const sessionId = event.data.object.id; + // from the session, we need to retrieve the line items const sessionWithLineItems = await stripe.checkout.sessions.retrieve( event.data.object.id, { @@ -280,6 +290,7 @@ export class StripeWebhookHandlerService price_amount: item.price?.unit_amount, interval: item.price?.recurring?.interval as string, interval_count: item.price?.recurring?.interval_count as number, + type: getLineItemTypeById(this.config, item.id), }; }); diff --git a/packages/database-webhooks/src/server/services/database-webhook-handler.service.ts b/packages/database-webhooks/src/server/services/database-webhook-handler.service.ts index 794b8dc1c..6ca8ea259 100644 --- a/packages/database-webhooks/src/server/services/database-webhook-handler.service.ts +++ b/packages/database-webhooks/src/server/services/database-webhook-handler.service.ts @@ -37,6 +37,7 @@ export class DatabaseWebhookHandlerService { const service = new DatabaseWebhookRouterService(client); try { + // handle the webhook event based on the table await service.handleWebhook(json); Logger.info( diff --git a/packages/database-webhooks/src/server/services/database-webhook-router.service.ts b/packages/database-webhooks/src/server/services/database-webhook-router.service.ts index c82b2f5eb..47b2885d4 100644 --- a/packages/database-webhooks/src/server/services/database-webhook-router.service.ts +++ b/packages/database-webhooks/src/server/services/database-webhook-router.service.ts @@ -1,5 +1,6 @@ import { SupabaseClient } from '@supabase/supabase-js'; +import { Logger } from '@kit/shared/logger'; import { Database } from '@kit/supabase/database'; import { RecordChange, Tables } from '../record-change.type'; @@ -21,14 +22,14 @@ export class DatabaseWebhookRouterService { return this.handleSubscriptionsWebhook(payload); } - case 'accounts_memberships': { - const payload = body as RecordChange; - - return this.handleAccountsMembershipsWebhook(payload); + default: { + Logger.warn( + { + table: body.table, + }, + 'No handler found for table', + ); } - - default: - throw new Error('No handler for this table'); } } @@ -52,12 +53,4 @@ export class DatabaseWebhookRouterService { return service.handleSubscriptionDeletedWebhook(body.old_record); } } - - private handleAccountsMembershipsWebhook( - payload: RecordChange<'accounts_memberships'>, - ) { - console.log('Accounts Memberships Webhook', payload); - // no-op - return Promise.resolve(undefined); - } } diff --git a/packages/features/accounts/src/server/services/delete-personal-account.service.ts b/packages/features/accounts/src/server/services/delete-personal-account.service.ts index 139f300e1..7d2baa01b 100644 --- a/packages/features/accounts/src/server/services/delete-personal-account.service.ts +++ b/packages/features/accounts/src/server/services/delete-personal-account.service.ts @@ -94,14 +94,13 @@ export class DeletePersonalAccountService { productName: string; }) { const { renderAccountDeleteEmail } = await import('@kit/email-templates'); - const mailer = new Mailer(); const html = renderAccountDeleteEmail({ userDisplayName: params.userDisplayName, productName: params.productName, }); - await mailer.sendEmail({ + await Mailer.sendEmail({ to: params.userEmail, from: params.fromEmail, subject: 'Account Deletion Request', diff --git a/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts b/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts index 273aa7f33..c4fbcc646 100644 --- a/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts +++ b/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts @@ -17,6 +17,7 @@ import { InviteMembersSchema } from '../../schema/invite-members.schema'; import { RenewInvitationSchema } from '../../schema/renew-invitation.schema'; import { UpdateInvitationSchema } from '../../schema/update-invitation.schema'; import { AccountInvitationsService } from '../services/account-invitations.service'; +import { AccountPerSeatBillingService } from '../services/account-per-seat-billing.service'; /** * Creates invitations for inviting members. @@ -98,15 +99,21 @@ export async function acceptInvitationAction(data: FormData) { Object.fromEntries(data), ); + const accountPerSeatBillingService = new AccountPerSeatBillingService(client); const user = await assertSession(client); const service = new AccountInvitationsService(client); - await service.acceptInvitationToTeam({ - adminClient: getSupabaseServerActionClient({ admin: true }), - inviteToken, - userId: user.id, - }); + // Accept the invitation + const accountId = await service.acceptInvitationToTeam( + getSupabaseServerActionClient({ admin: true }), + { + inviteToken, + userId: user.id, + }, + ); + + await accountPerSeatBillingService.increaseSeats(accountId); return redirect(nextPath); } diff --git a/packages/features/team-accounts/src/server/services/account-invitations-webhook.service.ts b/packages/features/team-accounts/src/server/services/account-invitations-webhook.service.ts index 9efa414ce..4b0e14ada 100644 --- a/packages/features/team-accounts/src/server/services/account-invitations-webhook.service.ts +++ b/packages/features/team-accounts/src/server/services/account-invitations-webhook.service.ts @@ -37,8 +37,6 @@ export class AccountInvitationsWebhookService { } private async dispatchInvitationEmail(invitation: Invitation) { - const mailer = new Mailer(); - const inviter = await this.client .from('accounts') .select('email, name') @@ -70,7 +68,7 @@ export class AccountInvitationsWebhookService { teamName: team.data.name, }); - await mailer.sendEmail({ + await Mailer.sendEmail({ from: env.emailSender, to: invitation.email, subject: 'You have been invited to join a team', diff --git a/packages/features/team-accounts/src/server/services/account-invitations.service.ts b/packages/features/team-accounts/src/server/services/account-invitations.service.ts index 646343dc5..3762aea42 100644 --- a/packages/features/team-accounts/src/server/services/account-invitations.service.ts +++ b/packages/features/team-accounts/src/server/services/account-invitations.service.ts @@ -12,7 +12,7 @@ import { InviteMembersSchema } from '../../schema/invite-members.schema'; import { UpdateInvitationSchema } from '../../schema/update-invitation.schema'; export class AccountInvitationsService { - private namespace = 'accounts.invitations'; + private readonly namespace = 'invitations'; constructor(private readonly client: SupabaseClient) {} @@ -76,7 +76,11 @@ export class AccountInvitationsService { accountSlug: string; }) { Logger.info( - { account: accountSlug, invitations, name: this.namespace }, + { + account: accountSlug, + invitations, + name: this.namespace, + }, 'Storing invitations', ); @@ -87,6 +91,14 @@ export class AccountInvitationsService { .single(); if (!accountResponse.data) { + Logger.error( + { + accountSlug, + name: this.namespace, + }, + 'Account not found in database. Cannot send invitations.', + ); + throw new Error('Account not found'); } @@ -96,6 +108,15 @@ export class AccountInvitationsService { }); if (response.error) { + Logger.error( + { + accountSlug, + error: response.error, + name: this.namespace, + }, + `Failed to add invitations to account ${accountSlug}`, + ); + throw response.error; } @@ -116,12 +137,14 @@ export class AccountInvitationsService { /** * Accepts an invitation to join a team. */ - async acceptInvitationToTeam(params: { - userId: string; - inviteToken: string; - adminClient: SupabaseClient; - }) { - const { error, data } = await params.adminClient.rpc('accept_invitation', { + async acceptInvitationToTeam( + adminClient: SupabaseClient, + params: { + userId: string; + inviteToken: string; + }, + ) { + const { error, data } = await adminClient.rpc('accept_invitation', { token: params.inviteToken, user_id: params.userId, }); diff --git a/packages/features/team-accounts/src/server/services/account-members.service.ts b/packages/features/team-accounts/src/server/services/account-members.service.ts index 9edd2a5cc..1867fcf96 100644 --- a/packages/features/team-accounts/src/server/services/account-members.service.ts +++ b/packages/features/team-accounts/src/server/services/account-members.service.ts @@ -9,6 +9,7 @@ import { Database } from '@kit/supabase/database'; import { RemoveMemberSchema } from '../../schema/remove-member.schema'; import { TransferOwnershipConfirmationSchema } from '../../schema/transfer-ownership-confirmation.schema'; import { UpdateMemberRoleSchema } from '../../schema/update-member-role.schema'; +import { AccountPerSeatBillingService } from './account-per-seat-billing.service'; export class AccountMembersService { private readonly namespace = 'account-members'; @@ -16,6 +17,13 @@ export class AccountMembersService { constructor(private readonly client: SupabaseClient) {} async removeMemberFromAccount(params: z.infer) { + const ctx = { + namespace: this.namespace, + ...params, + }; + + Logger.info(ctx, `Removing member from account...`); + const { data, error } = await this.client .from('accounts_memberships') .delete() @@ -25,13 +33,37 @@ export class AccountMembersService { }); if (error) { + Logger.error( + { + ...ctx, + error, + }, + `Failed to remove member from account`, + ); + throw error; } + Logger.info( + ctx, + `Successfully removed member from account. Verifying seat count...`, + ); + + const service = new AccountPerSeatBillingService(this.client); + + await service.decreaseSeats(params.accountId); + return data; } async updateMemberRole(params: z.infer) { + const ctx = { + namespace: this.namespace, + ...params, + }; + + Logger.info(ctx, `Updating member role...`); + const { data, error } = await this.client .from('accounts_memberships') .update({ @@ -43,9 +75,19 @@ export class AccountMembersService { }); if (error) { + Logger.error( + { + ...ctx, + error, + }, + `Failed to update member role`, + ); + throw error; } + Logger.info(ctx, `Successfully updated member role`); + return data; } @@ -57,7 +99,7 @@ export class AccountMembersService { ...params, }; - Logger.info(ctx, `Transferring ownership of account`); + Logger.info(ctx, `Transferring ownership of account...`); const { data, error } = await this.client.rpc( 'transfer_team_account_ownership', diff --git a/packages/features/team-accounts/src/server/services/account-per-seat-billing.service.ts b/packages/features/team-accounts/src/server/services/account-per-seat-billing.service.ts new file mode 100644 index 000000000..465dc993c --- /dev/null +++ b/packages/features/team-accounts/src/server/services/account-per-seat-billing.service.ts @@ -0,0 +1,206 @@ +import { SupabaseClient } from '@supabase/supabase-js'; + +import { BillingGatewayService } from '@kit/billing-gateway'; +import { Logger } from '@kit/shared/logger'; +import { Database } from '@kit/supabase/database'; + +export class AccountPerSeatBillingService { + private readonly namespace = 'accounts.per-seat-billing'; + + constructor(private readonly client: SupabaseClient) {} + + async getPerSeatSubscriptionItem(accountId: string) { + Logger.info( + { + name: this.namespace, + accountId, + }, + `Getting per-seat subscription item for account ${accountId}...`, + ); + + const { data, error } = await this.client + .from('subscriptions') + .select( + ` + provider: billing_provider, + id, + subscription_items !inner ( + quantity, + id: variant_id, + type + ) + `, + ) + .eq('account_id', accountId) + .eq('subscription_items.type', 'per-seat') + .maybeSingle(); + + if (error) { + Logger.info( + { + name: this.namespace, + accountId, + error, + }, + `Failed to get per-seat subscription item for account ${accountId}`, + ); + + throw error; + } + + if (!data?.subscription_items) { + Logger.info( + { name: this.namespace, accountId }, + `No per-seat subscription item found for account ${accountId}. Exiting...`, + ); + + return; + } + + Logger.info( + { + name: this.namespace, + accountId, + }, + `Per-seat subscription item found for account ${accountId}. Will update...`, + ); + + return data; + } + + async increaseSeats(accountId: string) { + const subscription = await this.getPerSeatSubscriptionItem(accountId); + + if (!subscription) { + return; + } + + const subscriptionItems = subscription.subscription_items.filter((item) => { + return item.type === 'per_seat'; + }); + + if (!subscriptionItems.length) { + return; + } + + const billingGateway = new BillingGatewayService(subscription.provider); + + Logger.info( + { + name: this.namespace, + accountId, + subscriptionItems, + }, + `Increasing seats for account ${accountId}...`, + ); + + const promises = subscriptionItems.map(async (item) => { + try { + Logger.info( + { + name: this.namespace, + accountId, + subscriptionItemId: item.id, + quantity: item.quantity + 1, + }, + `Updating subscription item...`, + ); + + await billingGateway.updateSubscriptionItem({ + subscriptionId: subscription.id, + subscriptionItemId: item.id, + quantity: item.quantity + 1, + }); + + Logger.info( + { + name: this.namespace, + accountId, + subscriptionItemId: item.id, + quantity: item.quantity + 1, + }, + `Subscription item updated successfully`, + ); + } catch (error) { + Logger.error( + { + name: this.namespace, + accountId, + error, + }, + `Failed to increase seats for account ${accountId}`, + ); + } + }); + + await Promise.all(promises); + } + + async decreaseSeats(accountId: string) { + const subscription = await this.getPerSeatSubscriptionItem(accountId); + + if (!subscription) { + return; + } + + const subscriptionItems = subscription.subscription_items.filter((item) => { + return item.type === 'per_seat'; + }); + + if (!subscriptionItems.length) { + return; + } + + Logger.info( + { + name: this.namespace, + accountId, + subscriptionItems, + }, + `Decreasing seats for account ${accountId}...`, + ); + + const billingGateway = new BillingGatewayService(subscription.provider); + + const promises = subscriptionItems.map(async (item) => { + try { + Logger.info( + { + name: this.namespace, + accountId, + subscriptionItemId: item.id, + quantity: item.quantity - 1, + }, + `Updating subscription item...`, + ); + + await billingGateway.updateSubscriptionItem({ + subscriptionId: subscription.id, + subscriptionItemId: item.id, + quantity: item.quantity - 1, + }); + + Logger.info( + { + name: this.namespace, + accountId, + subscriptionItemId: item.id, + quantity: item.quantity - 1, + }, + `Subscription item updated successfully`, + ); + } catch (error) { + Logger.error( + { + name: this.namespace, + accountId, + error, + }, + `Failed to decrease seats for account ${accountId}`, + ); + } + }); + + await Promise.all(promises); + } +} diff --git a/packages/mailers/README.md b/packages/mailers/README.md index aedc65aa7..8e72fe613 100644 --- a/packages/mailers/README.md +++ b/packages/mailers/README.md @@ -17,14 +17,20 @@ Make sure the app installs the `@kit/mailers` package before using it. ## Usage +By default, the package uses `nodemailer`. + +To use Cloudflare, please set the environment variable `MAILER_PROVIDER` to `cloudflare`. + +``` +MAILER_PROVIDER=cloudflare +``` + ### Send an email ```javascript import { Mailer } from '@kit/mailers'; -const mailer = new Mailer(); - -mailer.send({ +Mailer.sendEmail({ to: '', from: '', subject: 'Hello', diff --git a/packages/mailers/src/index.ts b/packages/mailers/src/index.ts index bc8cdef28..51f4af572 100644 --- a/packages/mailers/src/index.ts +++ b/packages/mailers/src/index.ts @@ -1,9 +1,9 @@ import { z } from 'zod'; -const MAILER_ENV = z +const MAILER_PROVIDER = z .enum(['nodemailer', 'cloudflare']) .default('nodemailer') - .parse(process.env.MAILER_ENV); + .parse(process.env.MAILER_PROVIDER); /** * @description A mailer interface that can be implemented by any mailer. @@ -27,7 +27,7 @@ export const Mailer = await getMailer(); * @description Get the mailer based on the environment variable. */ async function getMailer() { - switch (MAILER_ENV) { + switch (MAILER_PROVIDER) { case 'nodemailer': { const { Nodemailer } = await import('./impl/nodemailer'); @@ -41,6 +41,6 @@ async function getMailer() { } default: - throw new Error(`Invalid mailer environment: ${MAILER_ENV as string}`); + throw new Error(`Invalid mailer: ${MAILER_PROVIDER as string}`); } } diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index a5123aa34..01461a4d9 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -4,1213 +4,1218 @@ export type Json = | boolean | null | { [key: string]: Json | undefined } - | Json[]; + | Json[] export type Database = { graphql_public: { Tables: { - [_ in never]: never; - }; + [_ in never]: never + } Views: { - [_ in never]: never; - }; + [_ in never]: never + } Functions: { graphql: { Args: { - operationName?: string; - query?: string; - variables?: Json; - extensions?: Json; - }; - Returns: Json; - }; - }; + operationName?: string + query?: string + variables?: Json + extensions?: Json + } + Returns: Json + } + } Enums: { - [_ in never]: never; - }; + [_ in never]: never + } CompositeTypes: { - [_ in never]: never; - }; - }; + [_ in never]: never + } + } public: { Tables: { account_roles: { Row: { - account_id: string; - id: number; - role: string; - }; + account_id: string + id: number + role: string + } Insert: { - account_id: string; - id?: number; - role: string; - }; + account_id: string + id?: number + role: string + } Update: { - account_id?: string; - id?: number; - role?: string; - }; + account_id?: string + id?: number + role?: string + } Relationships: [ { - foreignKeyName: 'account_roles_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'accounts'; - referencedColumns: ['id']; + foreignKeyName: "account_roles_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'account_roles_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_account_workspace'; - referencedColumns: ['id']; + foreignKeyName: "account_roles_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] }, { - foreignKeyName: 'account_roles_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_accounts'; - referencedColumns: ['id']; + foreignKeyName: "account_roles_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'account_roles_role_fkey'; - columns: ['role']; - isOneToOne: false; - referencedRelation: 'roles'; - referencedColumns: ['name']; + foreignKeyName: "account_roles_role_fkey" + columns: ["role"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["name"] }, - ]; - }; + ] + } accounts: { Row: { - created_at: string | null; - created_by: string | null; - email: string | null; - id: string; - is_personal_account: boolean; - name: string; - picture_url: string | null; - primary_owner_user_id: string; - slug: string | null; - updated_at: string | null; - updated_by: string | null; - }; + created_at: string | null + created_by: string | null + email: string | null + id: string + is_personal_account: boolean + name: string + picture_url: string | null + primary_owner_user_id: string + slug: string | null + updated_at: string | null + updated_by: string | null + } Insert: { - created_at?: string | null; - created_by?: string | null; - email?: string | null; - id?: string; - is_personal_account?: boolean; - name: string; - picture_url?: string | null; - primary_owner_user_id?: string; - slug?: string | null; - updated_at?: string | null; - updated_by?: string | null; - }; + created_at?: string | null + created_by?: string | null + email?: string | null + id?: string + is_personal_account?: boolean + name: string + picture_url?: string | null + primary_owner_user_id?: string + slug?: string | null + updated_at?: string | null + updated_by?: string | null + } Update: { - created_at?: string | null; - created_by?: string | null; - email?: string | null; - id?: string; - is_personal_account?: boolean; - name?: string; - picture_url?: string | null; - primary_owner_user_id?: string; - slug?: string | null; - updated_at?: string | null; - updated_by?: string | null; - }; + created_at?: string | null + created_by?: string | null + email?: string | null + id?: string + is_personal_account?: boolean + name?: string + picture_url?: string | null + primary_owner_user_id?: string + slug?: string | null + updated_at?: string | null + updated_by?: string | null + } Relationships: [ { - foreignKeyName: 'accounts_created_by_fkey'; - columns: ['created_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "accounts_created_by_fkey" + columns: ["created_by"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_primary_owner_user_id_fkey'; - columns: ['primary_owner_user_id']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "accounts_primary_owner_user_id_fkey" + columns: ["primary_owner_user_id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_updated_by_fkey'; - columns: ['updated_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "accounts_updated_by_fkey" + columns: ["updated_by"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, - ]; - }; + ] + } accounts_memberships: { Row: { - account_id: string; - account_role: string; - created_at: string; - created_by: string | null; - updated_at: string; - updated_by: string | null; - user_id: string; - }; + account_id: string + account_role: string + created_at: string + created_by: string | null + updated_at: string + updated_by: string | null + user_id: string + } Insert: { - account_id: string; - account_role: string; - created_at?: string; - created_by?: string | null; - updated_at?: string; - updated_by?: string | null; - user_id: string; - }; + account_id: string + account_role: string + created_at?: string + created_by?: string | null + updated_at?: string + updated_by?: string | null + user_id: string + } Update: { - account_id?: string; - account_role?: string; - created_at?: string; - created_by?: string | null; - updated_at?: string; - updated_by?: string | null; - user_id?: string; - }; + account_id?: string + account_role?: string + created_at?: string + created_by?: string | null + updated_at?: string + updated_by?: string | null + user_id?: string + } Relationships: [ { - foreignKeyName: 'accounts_memberships_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'accounts'; - referencedColumns: ['id']; + foreignKeyName: "accounts_memberships_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_memberships_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_account_workspace'; - referencedColumns: ['id']; + foreignKeyName: "accounts_memberships_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_memberships_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_accounts'; - referencedColumns: ['id']; + foreignKeyName: "accounts_memberships_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_memberships_account_role_fkey'; - columns: ['account_role']; - isOneToOne: false; - referencedRelation: 'roles'; - referencedColumns: ['name']; + foreignKeyName: "accounts_memberships_account_role_fkey" + columns: ["account_role"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["name"] }, { - foreignKeyName: 'accounts_memberships_created_by_fkey'; - columns: ['created_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "accounts_memberships_created_by_fkey" + columns: ["created_by"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_memberships_updated_by_fkey'; - columns: ['updated_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "accounts_memberships_updated_by_fkey" + columns: ["updated_by"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, { - foreignKeyName: 'accounts_memberships_user_id_fkey'; - columns: ['user_id']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "accounts_memberships_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, - ]; - }; + ] + } billing_customers: { Row: { - account_id: string; - customer_id: string; - email: string | null; - id: number; - provider: Database['public']['Enums']['billing_provider']; - }; + account_id: string + customer_id: string + email: string | null + id: number + provider: Database["public"]["Enums"]["billing_provider"] + } Insert: { - account_id: string; - customer_id: string; - email?: string | null; - id?: number; - provider: Database['public']['Enums']['billing_provider']; - }; + account_id: string + customer_id: string + email?: string | null + id?: number + provider: Database["public"]["Enums"]["billing_provider"] + } Update: { - account_id?: string; - customer_id?: string; - email?: string | null; - id?: number; - provider?: Database['public']['Enums']['billing_provider']; - }; + account_id?: string + customer_id?: string + email?: string | null + id?: number + provider?: Database["public"]["Enums"]["billing_provider"] + } Relationships: [ { - foreignKeyName: 'billing_customers_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'accounts'; - referencedColumns: ['id']; + foreignKeyName: "billing_customers_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'billing_customers_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_account_workspace'; - referencedColumns: ['id']; + foreignKeyName: "billing_customers_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] }, { - foreignKeyName: 'billing_customers_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_accounts'; - referencedColumns: ['id']; + foreignKeyName: "billing_customers_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] }, - ]; - }; + ] + } config: { Row: { - billing_provider: Database['public']['Enums']['billing_provider']; - enable_account_billing: boolean; - enable_team_account_billing: boolean; - enable_team_accounts: boolean; - }; + billing_provider: Database["public"]["Enums"]["billing_provider"] + enable_account_billing: boolean + enable_team_account_billing: boolean + enable_team_accounts: boolean + } Insert: { - billing_provider?: Database['public']['Enums']['billing_provider']; - enable_account_billing?: boolean; - enable_team_account_billing?: boolean; - enable_team_accounts?: boolean; - }; + billing_provider?: Database["public"]["Enums"]["billing_provider"] + enable_account_billing?: boolean + enable_team_account_billing?: boolean + enable_team_accounts?: boolean + } Update: { - billing_provider?: Database['public']['Enums']['billing_provider']; - enable_account_billing?: boolean; - enable_team_account_billing?: boolean; - enable_team_accounts?: boolean; - }; - Relationships: []; - }; + billing_provider?: Database["public"]["Enums"]["billing_provider"] + enable_account_billing?: boolean + enable_team_account_billing?: boolean + enable_team_accounts?: boolean + } + Relationships: [] + } invitations: { Row: { - account_id: string; - created_at: string; - email: string; - expires_at: string; - id: number; - invite_token: string; - invited_by: string; - role: string; - updated_at: string; - }; + account_id: string + created_at: string + email: string + expires_at: string + id: number + invite_token: string + invited_by: string + role: string + updated_at: string + } Insert: { - account_id: string; - created_at?: string; - email: string; - expires_at?: string; - id?: number; - invite_token: string; - invited_by: string; - role: string; - updated_at?: string; - }; + account_id: string + created_at?: string + email: string + expires_at?: string + id?: number + invite_token: string + invited_by: string + role: string + updated_at?: string + } Update: { - account_id?: string; - created_at?: string; - email?: string; - expires_at?: string; - id?: number; - invite_token?: string; - invited_by?: string; - role?: string; - updated_at?: string; - }; + account_id?: string + created_at?: string + email?: string + expires_at?: string + id?: number + invite_token?: string + invited_by?: string + role?: string + updated_at?: string + } Relationships: [ { - foreignKeyName: 'invitations_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'accounts'; - referencedColumns: ['id']; + foreignKeyName: "invitations_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'invitations_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_account_workspace'; - referencedColumns: ['id']; + foreignKeyName: "invitations_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] }, { - foreignKeyName: 'invitations_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_accounts'; - referencedColumns: ['id']; + foreignKeyName: "invitations_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'invitations_invited_by_fkey'; - columns: ['invited_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; + foreignKeyName: "invitations_invited_by_fkey" + columns: ["invited_by"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] }, { - foreignKeyName: 'invitations_role_fkey'; - columns: ['role']; - isOneToOne: false; - referencedRelation: 'roles'; - referencedColumns: ['name']; + foreignKeyName: "invitations_role_fkey" + columns: ["role"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["name"] }, - ]; - }; + ] + } order_items: { Row: { - created_at: string; - order_id: string; - price_amount: number | null; - product_id: string; - quantity: number; - updated_at: string; - variant_id: string; - }; + created_at: string + order_id: string + price_amount: number | null + product_id: string + quantity: number + updated_at: string + variant_id: string + } Insert: { - created_at?: string; - order_id: string; - price_amount?: number | null; - product_id: string; - quantity?: number; - updated_at?: string; - variant_id: string; - }; + created_at?: string + order_id: string + price_amount?: number | null + product_id: string + quantity?: number + updated_at?: string + variant_id: string + } Update: { - created_at?: string; - order_id?: string; - price_amount?: number | null; - product_id?: string; - quantity?: number; - updated_at?: string; - variant_id?: string; - }; + created_at?: string + order_id?: string + price_amount?: number | null + product_id?: string + quantity?: number + updated_at?: string + variant_id?: string + } Relationships: [ { - foreignKeyName: 'order_items_order_id_fkey'; - columns: ['order_id']; - isOneToOne: false; - referencedRelation: 'orders'; - referencedColumns: ['id']; + foreignKeyName: "order_items_order_id_fkey" + columns: ["order_id"] + isOneToOne: false + referencedRelation: "orders" + referencedColumns: ["id"] }, - ]; - }; + ] + } orders: { Row: { - account_id: string; - billing_customer_id: number; - billing_provider: Database['public']['Enums']['billing_provider']; - created_at: string; - currency: string; - id: string; - status: Database['public']['Enums']['payment_status']; - total_amount: number; - updated_at: string; - }; + account_id: string + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + created_at: string + currency: string + id: string + status: Database["public"]["Enums"]["payment_status"] + total_amount: number + updated_at: string + } Insert: { - account_id: string; - billing_customer_id: number; - billing_provider: Database['public']['Enums']['billing_provider']; - created_at?: string; - currency: string; - id: string; - status: Database['public']['Enums']['payment_status']; - total_amount: number; - updated_at?: string; - }; + account_id: string + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + created_at?: string + currency: string + id: string + status: Database["public"]["Enums"]["payment_status"] + total_amount: number + updated_at?: string + } Update: { - account_id?: string; - billing_customer_id?: number; - billing_provider?: Database['public']['Enums']['billing_provider']; - created_at?: string; - currency?: string; - id?: string; - status?: Database['public']['Enums']['payment_status']; - total_amount?: number; - updated_at?: string; - }; + account_id?: string + billing_customer_id?: number + billing_provider?: Database["public"]["Enums"]["billing_provider"] + created_at?: string + currency?: string + id?: string + status?: Database["public"]["Enums"]["payment_status"] + total_amount?: number + updated_at?: string + } Relationships: [ { - foreignKeyName: 'orders_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'accounts'; - referencedColumns: ['id']; + foreignKeyName: "orders_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'orders_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_account_workspace'; - referencedColumns: ['id']; + foreignKeyName: "orders_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] }, { - foreignKeyName: 'orders_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_accounts'; - referencedColumns: ['id']; + foreignKeyName: "orders_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'orders_billing_customer_id_fkey'; - columns: ['billing_customer_id']; - isOneToOne: false; - referencedRelation: 'billing_customers'; - referencedColumns: ['id']; + foreignKeyName: "orders_billing_customer_id_fkey" + columns: ["billing_customer_id"] + isOneToOne: false + referencedRelation: "billing_customers" + referencedColumns: ["id"] }, - ]; - }; + ] + } role_permissions: { Row: { - id: number; - permission: Database['public']['Enums']['app_permissions']; - role: string; - }; + id: number + permission: Database["public"]["Enums"]["app_permissions"] + role: string + } Insert: { - id?: number; - permission: Database['public']['Enums']['app_permissions']; - role: string; - }; + id?: number + permission: Database["public"]["Enums"]["app_permissions"] + role: string + } Update: { - id?: number; - permission?: Database['public']['Enums']['app_permissions']; - role?: string; - }; + id?: number + permission?: Database["public"]["Enums"]["app_permissions"] + role?: string + } Relationships: [ { - foreignKeyName: 'role_permissions_role_fkey'; - columns: ['role']; - isOneToOne: false; - referencedRelation: 'roles'; - referencedColumns: ['name']; + foreignKeyName: "role_permissions_role_fkey" + columns: ["role"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["name"] }, - ]; - }; + ] + } roles: { Row: { - account_id: string | null; - hierarchy_level: number; - name: string; - }; + account_id: string | null + hierarchy_level: number + name: string + } Insert: { - account_id?: string | null; - hierarchy_level: number; - name: string; - }; + account_id?: string | null + hierarchy_level: number + name: string + } Update: { - account_id?: string | null; - hierarchy_level?: number; - name?: string; - }; + account_id?: string | null + hierarchy_level?: number + name?: string + } Relationships: [ { - foreignKeyName: 'roles_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'accounts'; - referencedColumns: ['id']; + foreignKeyName: "roles_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'roles_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_account_workspace'; - referencedColumns: ['id']; + foreignKeyName: "roles_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] }, { - foreignKeyName: 'roles_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_accounts'; - referencedColumns: ['id']; + foreignKeyName: "roles_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] }, - ]; - }; + ] + } subscription_items: { Row: { - created_at: string; - interval: string; - interval_count: number; - price_amount: number | null; - product_id: string; - quantity: number; - subscription_id: string; - updated_at: string; - variant_id: string; - }; + created_at: string + interval: string + interval_count: number + price_amount: number | null + product_id: string + quantity: number + subscription_id: string + type: Database["public"]["Enums"]["subscription_item_type"] + updated_at: string + variant_id: string + } Insert: { - created_at?: string; - interval: string; - interval_count: number; - price_amount?: number | null; - product_id: string; - quantity?: number; - subscription_id: string; - updated_at?: string; - variant_id: string; - }; + created_at?: string + interval: string + interval_count: number + price_amount?: number | null + product_id: string + quantity?: number + subscription_id: string + type: Database["public"]["Enums"]["subscription_item_type"] + updated_at?: string + variant_id: string + } Update: { - created_at?: string; - interval?: string; - interval_count?: number; - price_amount?: number | null; - product_id?: string; - quantity?: number; - subscription_id?: string; - updated_at?: string; - variant_id?: string; - }; + created_at?: string + interval?: string + interval_count?: number + price_amount?: number | null + product_id?: string + quantity?: number + subscription_id?: string + type?: Database["public"]["Enums"]["subscription_item_type"] + updated_at?: string + variant_id?: string + } Relationships: [ { - foreignKeyName: 'subscription_items_subscription_id_fkey'; - columns: ['subscription_id']; - isOneToOne: false; - referencedRelation: 'subscriptions'; - referencedColumns: ['id']; + foreignKeyName: "subscription_items_subscription_id_fkey" + columns: ["subscription_id"] + isOneToOne: false + referencedRelation: "subscriptions" + referencedColumns: ["id"] }, - ]; - }; + ] + } subscriptions: { Row: { - account_id: string; - active: boolean; - billing_customer_id: number; - billing_provider: Database['public']['Enums']['billing_provider']; - cancel_at_period_end: boolean; - created_at: string; - currency: string; - id: string; - period_ends_at: string; - period_starts_at: string; - status: Database['public']['Enums']['subscription_status']; - trial_ends_at: string | null; - trial_starts_at: string | null; - updated_at: string; - }; + account_id: string + active: boolean + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end: boolean + created_at: string + currency: string + id: string + period_ends_at: string + period_starts_at: string + status: Database["public"]["Enums"]["subscription_status"] + trial_ends_at: string | null + trial_starts_at: string | null + updated_at: string + } Insert: { - account_id: string; - active: boolean; - billing_customer_id: number; - billing_provider: Database['public']['Enums']['billing_provider']; - cancel_at_period_end: boolean; - created_at?: string; - currency: string; - id: string; - period_ends_at: string; - period_starts_at: string; - status: Database['public']['Enums']['subscription_status']; - trial_ends_at?: string | null; - trial_starts_at?: string | null; - updated_at?: string; - }; + account_id: string + active: boolean + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end: boolean + created_at?: string + currency: string + id: string + period_ends_at: string + period_starts_at: string + status: Database["public"]["Enums"]["subscription_status"] + trial_ends_at?: string | null + trial_starts_at?: string | null + updated_at?: string + } Update: { - account_id?: string; - active?: boolean; - billing_customer_id?: number; - billing_provider?: Database['public']['Enums']['billing_provider']; - cancel_at_period_end?: boolean; - created_at?: string; - currency?: string; - id?: string; - period_ends_at?: string; - period_starts_at?: string; - status?: Database['public']['Enums']['subscription_status']; - trial_ends_at?: string | null; - trial_starts_at?: string | null; - updated_at?: string; - }; + account_id?: string + active?: boolean + billing_customer_id?: number + billing_provider?: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end?: boolean + created_at?: string + currency?: string + id?: string + period_ends_at?: string + period_starts_at?: string + status?: Database["public"]["Enums"]["subscription_status"] + trial_ends_at?: string | null + trial_starts_at?: string | null + updated_at?: string + } Relationships: [ { - foreignKeyName: 'subscriptions_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'accounts'; - referencedColumns: ['id']; + foreignKeyName: "subscriptions_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'subscriptions_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_account_workspace'; - referencedColumns: ['id']; + foreignKeyName: "subscriptions_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] }, { - foreignKeyName: 'subscriptions_account_id_fkey'; - columns: ['account_id']; - isOneToOne: false; - referencedRelation: 'user_accounts'; - referencedColumns: ['id']; + foreignKeyName: "subscriptions_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] }, { - foreignKeyName: 'subscriptions_billing_customer_id_fkey'; - columns: ['billing_customer_id']; - isOneToOne: false; - referencedRelation: 'billing_customers'; - referencedColumns: ['id']; + foreignKeyName: "subscriptions_billing_customer_id_fkey" + columns: ["billing_customer_id"] + isOneToOne: false + referencedRelation: "billing_customers" + referencedColumns: ["id"] }, - ]; - }; - }; + ] + } + } Views: { user_account_workspace: { Row: { - id: string | null; - name: string | null; - picture_url: string | null; + id: string | null + name: string | null + picture_url: string | null subscription_status: - | Database['public']['Enums']['subscription_status'] - | null; - }; - Relationships: []; - }; + | Database["public"]["Enums"]["subscription_status"] + | null + } + Relationships: [] + } user_accounts: { Row: { - id: string | null; - name: string | null; - picture_url: string | null; - role: string | null; - slug: string | null; - }; + id: string | null + name: string | null + picture_url: string | null + role: string | null + slug: string | null + } Relationships: [ { - foreignKeyName: 'accounts_memberships_account_role_fkey'; - columns: ['role']; - isOneToOne: false; - referencedRelation: 'roles'; - referencedColumns: ['name']; + foreignKeyName: "accounts_memberships_account_role_fkey" + columns: ["role"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["name"] }, - ]; - }; - }; + ] + } + } Functions: { accept_invitation: { Args: { - token: string; - user_id: string; - }; - Returns: undefined; - }; + token: string + user_id: string + } + Returns: string + } add_invitations_to_account: { Args: { - account_slug: string; - invitations: unknown[]; - }; - Returns: Database['public']['Tables']['invitations']['Row'][]; - }; + account_slug: string + invitations: unknown[] + } + Returns: Database["public"]["Tables"]["invitations"]["Row"][] + } create_account: { Args: { - account_name: string; - }; + account_name: string + } Returns: { - created_at: string | null; - created_by: string | null; - email: string | null; - id: string; - is_personal_account: boolean; - name: string; - picture_url: string | null; - primary_owner_user_id: string; - slug: string | null; - updated_at: string | null; - updated_by: string | null; - }; - }; + created_at: string | null + created_by: string | null + email: string | null + id: string + is_personal_account: boolean + name: string + picture_url: string | null + primary_owner_user_id: string + slug: string | null + updated_at: string | null + updated_by: string | null + } + } create_invitation: { Args: { - account_id: string; - email: string; - role: string; - }; + account_id: string + email: string + role: string + } Returns: { - account_id: string; - created_at: string; - email: string; - expires_at: string; - id: number; - invite_token: string; - invited_by: string; - role: string; - updated_at: string; - }; - }; + account_id: string + created_at: string + email: string + expires_at: string + id: number + invite_token: string + invited_by: string + role: string + updated_at: string + } + } get_account_invitations: { Args: { - account_slug: string; - }; + account_slug: string + } Returns: { - id: number; - email: string; - account_id: string; - invited_by: string; - role: string; - created_at: string; - updated_at: string; - expires_at: string; - inviter_name: string; - inviter_email: string; - }[]; - }; + id: number + email: string + account_id: string + invited_by: string + role: string + created_at: string + updated_at: string + expires_at: string + inviter_name: string + inviter_email: string + }[] + } get_account_members: { Args: { - account_slug: string; - }; + account_slug: string + } Returns: { - id: string; - user_id: string; - account_id: string; - role: string; - role_hierarchy_level: number; - primary_owner_user_id: string; - name: string; - email: string; - picture_url: string; - created_at: string; - updated_at: string; - }[]; - }; + id: string + user_id: string + account_id: string + role: string + role_hierarchy_level: number + primary_owner_user_id: string + name: string + email: string + picture_url: string + created_at: string + updated_at: string + }[] + } get_config: { - Args: Record; - Returns: Json; - }; + Args: Record + Returns: Json + } get_user_accounts: { - Args: Record; + Args: Record Returns: { - created_at: string | null; - created_by: string | null; - email: string | null; - id: string; - is_personal_account: boolean; - name: string; - picture_url: string | null; - primary_owner_user_id: string; - slug: string | null; - updated_at: string | null; - updated_by: string | null; - }[]; - }; + created_at: string | null + created_by: string | null + email: string | null + id: string + is_personal_account: boolean + name: string + picture_url: string | null + primary_owner_user_id: string + slug: string | null + updated_at: string | null + updated_by: string | null + }[] + } has_more_elevated_role: { Args: { - target_user_id: string; - target_account_id: string; - role_name: string; - }; - Returns: boolean; - }; + target_user_id: string + target_account_id: string + role_name: string + } + Returns: boolean + } has_permission: { Args: { - user_id: string; - account_id: string; - permission_name: Database['public']['Enums']['app_permissions']; - }; - Returns: boolean; - }; + user_id: string + account_id: string + permission_name: Database["public"]["Enums"]["app_permissions"] + } + Returns: boolean + } has_role_on_account: { Args: { - account_id: string; - account_role?: string; - }; - Returns: boolean; - }; + account_id: string + account_role?: string + } + Returns: boolean + } is_account_owner: { Args: { - account_id: string; - }; - Returns: boolean; - }; + account_id: string + } + Returns: boolean + } is_set: { Args: { - field_name: string; - }; - Returns: boolean; - }; + field_name: string + } + Returns: boolean + } is_team_member: { Args: { - account_id: string; - user_id: string; - }; - Returns: boolean; - }; + account_id: string + user_id: string + } + Returns: boolean + } organization_account_workspace: { Args: { - account_slug: string; - }; + account_slug: string + } Returns: { - id: string; - name: string; - picture_url: string; - slug: string; - role: string; - role_hierarchy_level: number; - primary_owner_user_id: string; - subscription_status: Database['public']['Enums']['subscription_status']; - permissions: Database['public']['Enums']['app_permissions'][]; - }[]; - }; + id: string + name: string + picture_url: string + slug: string + role: string + role_hierarchy_level: number + primary_owner_user_id: string + subscription_status: Database["public"]["Enums"]["subscription_status"] + permissions: Database["public"]["Enums"]["app_permissions"][] + }[] + } transfer_team_account_ownership: { Args: { - target_account_id: string; - new_owner_id: string; - }; - Returns: undefined; - }; + target_account_id: string + new_owner_id: string + } + Returns: undefined + } unaccent: { Args: { - '': string; - }; - Returns: string; - }; + "": string + } + Returns: string + } unaccent_init: { Args: { - '': unknown; - }; - Returns: unknown; - }; + "": unknown + } + Returns: unknown + } upsert_order: { Args: { - target_account_id: string; - target_customer_id: string; - target_order_id: string; - status: Database['public']['Enums']['payment_status']; - billing_provider: Database['public']['Enums']['billing_provider']; - total_amount: number; - currency: string; - line_items: Json; - }; + target_account_id: string + target_customer_id: string + target_order_id: string + status: Database["public"]["Enums"]["payment_status"] + billing_provider: Database["public"]["Enums"]["billing_provider"] + total_amount: number + currency: string + line_items: Json + } Returns: { - account_id: string; - billing_customer_id: number; - billing_provider: Database['public']['Enums']['billing_provider']; - created_at: string; - currency: string; - id: string; - status: Database['public']['Enums']['payment_status']; - total_amount: number; - updated_at: string; - }; - }; + account_id: string + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + created_at: string + currency: string + id: string + status: Database["public"]["Enums"]["payment_status"] + total_amount: number + updated_at: string + } + } upsert_subscription: { Args: { - target_account_id: string; - target_customer_id: string; - target_subscription_id: string; - active: boolean; - status: Database['public']['Enums']['subscription_status']; - billing_provider: Database['public']['Enums']['billing_provider']; - cancel_at_period_end: boolean; - currency: string; - period_starts_at: string; - period_ends_at: string; - line_items: Json; - trial_starts_at?: string; - trial_ends_at?: string; - }; + target_account_id: string + target_customer_id: string + target_subscription_id: string + active: boolean + status: Database["public"]["Enums"]["subscription_status"] + billing_provider: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end: boolean + currency: string + period_starts_at: string + period_ends_at: string + line_items: Json + trial_starts_at?: string + trial_ends_at?: string + } Returns: { - account_id: string; - active: boolean; - billing_customer_id: number; - billing_provider: Database['public']['Enums']['billing_provider']; - cancel_at_period_end: boolean; - created_at: string; - currency: string; - id: string; - period_ends_at: string; - period_starts_at: string; - status: Database['public']['Enums']['subscription_status']; - trial_ends_at: string | null; - trial_starts_at: string | null; - updated_at: string; - }; - }; - }; + account_id: string + active: boolean + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end: boolean + created_at: string + currency: string + id: string + period_ends_at: string + period_starts_at: string + status: Database["public"]["Enums"]["subscription_status"] + trial_ends_at: string | null + trial_starts_at: string | null + updated_at: string + } + } + } Enums: { app_permissions: - | 'roles.manage' - | 'billing.manage' - | 'settings.manage' - | 'members.manage' - | 'invites.manage'; - billing_provider: 'stripe' | 'lemon-squeezy' | 'paddle'; - payment_status: 'pending' | 'succeeded' | 'failed'; + | "roles.manage" + | "billing.manage" + | "settings.manage" + | "members.manage" + | "invites.manage" + billing_provider: "stripe" | "lemon-squeezy" | "paddle" + payment_status: "pending" | "succeeded" | "failed" + subscription_item_type: "base" | "per_seat" | "metered" subscription_status: - | 'active' - | 'trialing' - | 'past_due' - | 'canceled' - | 'unpaid' - | 'incomplete' - | 'incomplete_expired' - | 'paused'; - }; + | "active" + | "trialing" + | "past_due" + | "canceled" + | "unpaid" + | "incomplete" + | "incomplete_expired" + | "paused" + } CompositeTypes: { - [_ in never]: never; - }; - }; + [_ in never]: never + } + } storage: { Tables: { buckets: { Row: { - allowed_mime_types: string[] | null; - avif_autodetection: boolean | null; - created_at: string | null; - file_size_limit: number | null; - id: string; - name: string; - owner: string | null; - owner_id: string | null; - public: boolean | null; - updated_at: string | null; - }; + allowed_mime_types: string[] | null + avif_autodetection: boolean | null + created_at: string | null + file_size_limit: number | null + id: string + name: string + owner: string | null + owner_id: string | null + public: boolean | null + updated_at: string | null + } Insert: { - allowed_mime_types?: string[] | null; - avif_autodetection?: boolean | null; - created_at?: string | null; - file_size_limit?: number | null; - id: string; - name: string; - owner?: string | null; - owner_id?: string | null; - public?: boolean | null; - updated_at?: string | null; - }; + allowed_mime_types?: string[] | null + avif_autodetection?: boolean | null + created_at?: string | null + file_size_limit?: number | null + id: string + name: string + owner?: string | null + owner_id?: string | null + public?: boolean | null + updated_at?: string | null + } Update: { - allowed_mime_types?: string[] | null; - avif_autodetection?: boolean | null; - created_at?: string | null; - file_size_limit?: number | null; - id?: string; - name?: string; - owner?: string | null; - owner_id?: string | null; - public?: boolean | null; - updated_at?: string | null; - }; - Relationships: []; - }; + allowed_mime_types?: string[] | null + avif_autodetection?: boolean | null + created_at?: string | null + file_size_limit?: number | null + id?: string + name?: string + owner?: string | null + owner_id?: string | null + public?: boolean | null + updated_at?: string | null + } + Relationships: [] + } migrations: { Row: { - executed_at: string | null; - hash: string; - id: number; - name: string; - }; + executed_at: string | null + hash: string + id: number + name: string + } Insert: { - executed_at?: string | null; - hash: string; - id: number; - name: string; - }; + executed_at?: string | null + hash: string + id: number + name: string + } Update: { - executed_at?: string | null; - hash?: string; - id?: number; - name?: string; - }; - Relationships: []; - }; + executed_at?: string | null + hash?: string + id?: number + name?: string + } + Relationships: [] + } objects: { Row: { - bucket_id: string | null; - created_at: string | null; - id: string; - last_accessed_at: string | null; - metadata: Json | null; - name: string | null; - owner: string | null; - owner_id: string | null; - path_tokens: string[] | null; - updated_at: string | null; - version: string | null; - }; + bucket_id: string | null + created_at: string | null + id: string + last_accessed_at: string | null + metadata: Json | null + name: string | null + owner: string | null + owner_id: string | null + path_tokens: string[] | null + updated_at: string | null + version: string | null + } Insert: { - bucket_id?: string | null; - created_at?: string | null; - id?: string; - last_accessed_at?: string | null; - metadata?: Json | null; - name?: string | null; - owner?: string | null; - owner_id?: string | null; - path_tokens?: string[] | null; - updated_at?: string | null; - version?: string | null; - }; + bucket_id?: string | null + created_at?: string | null + id?: string + last_accessed_at?: string | null + metadata?: Json | null + name?: string | null + owner?: string | null + owner_id?: string | null + path_tokens?: string[] | null + updated_at?: string | null + version?: string | null + } Update: { - bucket_id?: string | null; - created_at?: string | null; - id?: string; - last_accessed_at?: string | null; - metadata?: Json | null; - name?: string | null; - owner?: string | null; - owner_id?: string | null; - path_tokens?: string[] | null; - updated_at?: string | null; - version?: string | null; - }; + bucket_id?: string | null + created_at?: string | null + id?: string + last_accessed_at?: string | null + metadata?: Json | null + name?: string | null + owner?: string | null + owner_id?: string | null + path_tokens?: string[] | null + updated_at?: string | null + version?: string | null + } Relationships: [ { - foreignKeyName: 'objects_bucketId_fkey'; - columns: ['bucket_id']; - isOneToOne: false; - referencedRelation: 'buckets'; - referencedColumns: ['id']; + foreignKeyName: "objects_bucketId_fkey" + columns: ["bucket_id"] + isOneToOne: false + referencedRelation: "buckets" + referencedColumns: ["id"] }, - ]; - }; - }; + ] + } + } Views: { - [_ in never]: never; - }; + [_ in never]: never + } Functions: { can_insert_object: { Args: { - bucketid: string; - name: string; - owner: string; - metadata: Json; - }; - Returns: undefined; - }; + bucketid: string + name: string + owner: string + metadata: Json + } + Returns: undefined + } extension: { Args: { - name: string; - }; - Returns: string; - }; + name: string + } + Returns: string + } filename: { Args: { - name: string; - }; - Returns: string; - }; + name: string + } + Returns: string + } foldername: { Args: { - name: string; - }; - Returns: string[]; - }; + name: string + } + Returns: string[] + } get_size_by_bucket: { - Args: Record; + Args: Record Returns: { - size: number; - bucket_id: string; - }[]; - }; + size: number + bucket_id: string + }[] + } search: { Args: { - prefix: string; - bucketname: string; - limits?: number; - levels?: number; - offsets?: number; - search?: string; - sortcolumn?: string; - sortorder?: string; - }; + prefix: string + bucketname: string + limits?: number + levels?: number + offsets?: number + search?: string + sortcolumn?: string + sortorder?: string + } Returns: { - name: string; - id: string; - updated_at: string; - created_at: string; - last_accessed_at: string; - metadata: Json; - }[]; - }; - }; + name: string + id: string + updated_at: string + created_at: string + last_accessed_at: string + metadata: Json + }[] + } + } Enums: { - [_ in never]: never; - }; + [_ in never]: never + } CompositeTypes: { - [_ in never]: never; - }; - }; -}; + [_ in never]: never + } + } +} -type PublicSchema = Database[Extract]; +type PublicSchema = Database[Extract] export type Tables< PublicTableNameOrOptions extends - | keyof (PublicSchema['Tables'] & PublicSchema['Views']) + | keyof (PublicSchema["Tables"] & PublicSchema["Views"]) | { schema: keyof Database }, TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof (Database[PublicTableNameOrOptions['schema']]['Tables'] & - Database[PublicTableNameOrOptions['schema']]['Views']) + ? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] & + Database[PublicTableNameOrOptions["schema"]]["Views"]) : never = never, > = PublicTableNameOrOptions extends { schema: keyof Database } - ? (Database[PublicTableNameOrOptions['schema']]['Tables'] & - Database[PublicTableNameOrOptions['schema']]['Views'])[TableName] extends { - Row: infer R; + ? (Database[PublicTableNameOrOptions["schema"]]["Tables"] & + Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R } ? R : never - : PublicTableNameOrOptions extends keyof (PublicSchema['Tables'] & - PublicSchema['Views']) - ? (PublicSchema['Tables'] & - PublicSchema['Views'])[PublicTableNameOrOptions] extends { - Row: infer R; + : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] & + PublicSchema["Views"]) + ? (PublicSchema["Tables"] & + PublicSchema["Views"])[PublicTableNameOrOptions] extends { + Row: infer R } ? R : never - : never; + : never export type TablesInsert< PublicTableNameOrOptions extends - | keyof PublicSchema['Tables'] + | keyof PublicSchema["Tables"] | { schema: keyof Database }, TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] : never = never, > = PublicTableNameOrOptions extends { schema: keyof Database } - ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { - Insert: infer I; + ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I } ? I : never - : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] - ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { - Insert: infer I; + : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] + ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { + Insert: infer I } ? I : never - : never; + : never export type TablesUpdate< PublicTableNameOrOptions extends - | keyof PublicSchema['Tables'] + | keyof PublicSchema["Tables"] | { schema: keyof Database }, TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] : never = never, > = PublicTableNameOrOptions extends { schema: keyof Database } - ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { - Update: infer U; + ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U } ? U : never - : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] - ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { - Update: infer U; + : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] + ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { + Update: infer U } ? U : never - : never; + : never export type Enums< PublicEnumNameOrOptions extends - | keyof PublicSchema['Enums'] + | keyof PublicSchema["Enums"] | { schema: keyof Database }, EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicEnumNameOrOptions['schema']]['Enums'] + ? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"] : never = never, > = PublicEnumNameOrOptions extends { schema: keyof Database } - ? Database[PublicEnumNameOrOptions['schema']]['Enums'][EnumName] - : PublicEnumNameOrOptions extends keyof PublicSchema['Enums'] - ? PublicSchema['Enums'][PublicEnumNameOrOptions] - : never; + ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName] + : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"] + ? PublicSchema["Enums"][PublicEnumNameOrOptions] + : never + diff --git a/supabase/migrations/20221215192558_schema.sql b/supabase/migrations/20221215192558_schema.sql index 0ff78fe63..f3efa2952 100644 --- a/supabase/migrations/20221215192558_schema.sql +++ b/supabase/migrations/20221215192558_schema.sql @@ -116,6 +116,17 @@ create type public.billing_provider as ENUM( 'paddle' ); +/* +* Subscription Item Type +- We create the subscription item type for the Supabase MakerKit. These types are used to manage the type of the subscription items +- The types are 'flat', 'per_seat', and 'metered'. +- You can add more types as needed. +*/ +create type public.subscription_item_type as ENUM( + 'base', + 'per_seat', + 'metered' +); /* * ------------------------------------------------------- @@ -1023,7 +1034,7 @@ create policy invitations_delete on public.invitations -- Functions -- Function to accept an invitation to an account create or replace function accept_invitation(token text, user_id uuid) - returns void + returns uuid as $$ declare target_account_id uuid; @@ -1056,6 +1067,7 @@ begin delete from public.invitations where invite_token = token; + return target_account_id; end; $$ @@ -1248,17 +1260,19 @@ on conflict ( with item_data as ( select (line_item ->> 'product_id')::varchar as prod_id, -(line_item ->> 'variant_id')::varchar as var_id, -(line_item ->> 'price_amount')::numeric as price_amt, -(line_item ->> 'quantity')::integer as qty, -(line_item ->> 'interval')::varchar as intv, -(line_item ->> 'interval_count')::integer as intv_count + (line_item ->> 'variant_id')::varchar as var_id, + (line_item ->> 'type')::public.subscription_item_type as type, + (line_item ->> 'price_amount')::numeric as price_amt, + (line_item ->> 'quantity')::integer as qty, + (line_item ->> 'interval')::varchar as intv, + (line_item ->> 'interval_count')::integer as intv_count from jsonb_array_elements(line_items) as line_item) insert into public.subscription_items( subscription_id, product_id, variant_id, + type, price_amount, quantity, interval, @@ -1267,6 +1281,7 @@ on conflict ( target_subscription_id, prod_id, var_id, + type, price_amt, qty, intv, @@ -1306,6 +1321,7 @@ create table if not exists public.subscription_items( delete cascade not null, product_id varchar(255) not null, variant_id varchar(255) not null, + type public.subscription_item_type not null, price_amount numeric, quantity integer not null default 1, interval varchar(255) not null,