@@ -2,11 +2,10 @@ import type { User as AuthJsUser } from "next-auth";
22import { __unsafePrisma } from "@/prisma" ;
33import { OrgRole } from "@sourcebot/db" ;
44import { SINGLE_TENANT_ORG_ID , SOURCEBOT_GUEST_USER_EMAIL , SOURCEBOT_GUEST_USER_ID , SOURCEBOT_SUPPORT_EMAIL } from "@/lib/constants" ;
5- import { SOURCEBOT_UNLIMITED_SEATS } from "@sourcebot/shared" ;
6- import { getSeats , hasEntitlement } from "@/lib/entitlements" ;
5+ import { hasEntitlement } from "@/lib/entitlements" ;
76import { isServiceError } from "@/lib/utils" ;
87import { orgNotFound , ServiceError , userNotFound } from "@/lib/serviceError" ;
9- import { createLogger } from "@sourcebot/shared" ;
8+ import { createLogger , getOfflineLicenseKey } from "@sourcebot/shared" ;
109import { createAudit } from "@/ee/features/audit/audit" ;
1110import { StatusCodes } from "http-status-codes" ;
1211import { ErrorCode } from "./errorCodes" ;
@@ -49,7 +48,6 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
4948 }
5049 } ) ;
5150
52- // We expect the default org to have been created on app initialization
5351 if ( defaultOrg === null ) {
5452 await createAudit ( {
5553 action : "user.creation_failed" ,
@@ -69,7 +67,8 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
6967 throw new Error ( "Default org not found on single tenant user creation" ) ;
7068 }
7169
72- // If this is the first user to sign up, we make them the owner of the default org.
70+ // First (non-guest) user to sign up bootstraps the org as its OWNER. This
71+ // is how a fresh deployment gets its initial admin without manual setup.
7372 const isFirstUser = defaultOrg . members . length === 0 ;
7473 if ( isFirstUser ) {
7574 await __unsafePrisma . $transaction ( async ( tx ) => {
@@ -104,8 +103,16 @@ export const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
104103 type : "org"
105104 }
106105 } ) ;
107- } else if ( ! defaultOrg . memberApprovalRequired ) {
108- const hasAvailability = await orgHasAvailability ( ) ;
106+ }
107+
108+ // Subsequent users auto-join as MEMBER only when the org is in open
109+ // self-serve mode. If memberApprovalRequired is true, the user is left
110+ // without a membership and must submit an AccountRequest for an owner to
111+ // approve via addUserToOrganization.
112+ else if ( ! defaultOrg . memberApprovalRequired ) {
113+ // Don't exceed the licensed seat count. The user row still exists;
114+ // they just aren't attached to the org until a seat frees up.
115+ const hasAvailability = await orgHasAvailability ( defaultOrg . id ) ;
109116 if ( ! hasAvailability ) {
110117 logger . warn ( `onCreateUser: org ${ SINGLE_TENANT_ORG_ID } has reached max capacity. User ${ user . id } was not added to the org.` ) ;
111118 return ;
@@ -189,30 +196,31 @@ export const createGuestUser = async (): Promise<ServiceError | boolean> => {
189196 return true ;
190197} ;
191198
192- export const orgHasAvailability = async ( ) : Promise < boolean > => {
193- const org = await __unsafePrisma . org . findUnique ( {
199+ /**
200+ * Checks to see if the given organization has seat availability.
201+ * Seat availability is determined by the `seats` parameter in
202+ * the offline license key, if available.
203+ */
204+ export const orgHasAvailability = async ( orgId : number ) : Promise < boolean > => {
205+ const org = await __unsafePrisma . org . findUniqueOrThrow ( {
194206 where : {
195- id : SINGLE_TENANT_ORG_ID ,
207+ id : orgId ,
196208 } ,
197- } ) ;
198-
199- if ( ! org ) {
200- logger . error ( `orgHasAvailability: org not found for id ${ SINGLE_TENANT_ORG_ID } ` ) ;
201- return false ;
202- }
203- const members = await __unsafePrisma . userToOrg . findMany ( {
204- where : {
205- orgId : org . id ,
206- role : {
207- not : OrgRole . GUEST ,
209+ include : {
210+ members : {
211+ where : {
212+ role : {
213+ not : OrgRole . GUEST
214+ }
215+ }
208216 } ,
209- } ,
217+ }
210218 } ) ;
211219
212- const maxSeats = await getSeats ( ) ;
213- const memberCount = members . length ;
220+ const licenseKey = getOfflineLicenseKey ( ) ;
221+ const memberCount = org . members . length ;
214222
215- if ( maxSeats !== SOURCEBOT_UNLIMITED_SEATS && memberCount >= maxSeats ) {
223+ if ( licenseKey && memberCount >= licenseKey ?. seats ) {
216224 logger . error ( `orgHasAvailability: org ${ org . id } has reached max capacity` ) ;
217225 return false ;
218226 }
@@ -243,7 +251,7 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom
243251 return orgNotFound ( ) ;
244252 }
245253
246- const hasAvailability = await orgHasAvailability ( ) ;
254+ const hasAvailability = await orgHasAvailability ( org . id ) ;
247255 if ( ! hasAvailability ) {
248256 return {
249257 statusCode : StatusCodes . BAD_REQUEST ,
0 commit comments