spine-framework 0.3.59 → 0.3.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -309,7 +309,8 @@ interface SpineConfig {
309
309
  default_role?: string
310
310
  allow_url_override?: boolean
311
311
  apps?: Record<string, {
312
- role: string
312
+ roles: string[]
313
+ default_role: string
313
314
  path: string
314
315
  enabled: boolean
315
316
  }>
@@ -344,9 +345,25 @@ function updateRegistrationConfig(appSlug: string, regConfig: RegistrationConfig
344
345
  config.registration.apps = {}
345
346
  }
346
347
 
347
- // Add/update this app's registration entry
348
+ // Try to load all roles from the app's seed data
349
+ let availableRoles: string[] = [regConfig.default_role] // Start with default role from manifest
350
+
351
+ try {
352
+ const rolesPath = join(PROJECT_ROOT, 'custom/apps', appSlug, 'seed/roles.json')
353
+ if (existsSync(rolesPath)) {
354
+ const rolesData = JSON.parse(readFileSync(rolesPath, 'utf8'))
355
+ if (Array.isArray(rolesData)) {
356
+ availableRoles = rolesData.map((role: any) => role.slug).filter(Boolean)
357
+ }
358
+ }
359
+ } catch (error) {
360
+ console.log(` ⚠️ Could not load roles from seed data, using default role only`)
361
+ }
362
+
363
+ // Add/update this app's registration entry with multi-role support
348
364
  config.registration.apps[appSlug] = {
349
- role: regConfig.default_role,
365
+ roles: availableRoles,
366
+ default_role: regConfig.default_role,
350
367
  path: regConfig.redirect_path,
351
368
  enabled: regConfig.enabled,
352
369
  }
@@ -365,4 +382,5 @@ function updateRegistrationConfig(appSlug: string, regConfig: RegistrationConfig
365
382
 
366
383
  saveSpineConfig(config)
367
384
  console.log(` ✓ Updated spine.config.json registration for ${appSlug}`)
385
+ console.log(` ✓ Available roles: ${availableRoles.join(', ')}`)
368
386
  }
@@ -214,7 +214,6 @@ export const validateToken = createHandler(async (_ctx, _body) => {
214
214
  */
215
215
  export const completeRegistration = createHandler(async (ctx, body) => {
216
216
  const { path, account_name, email, role_slug } = body
217
- console.log('completeRegistration called with:', { path, account_name, email, role_slug })
218
217
 
219
218
  if (!path || !email) {
220
219
  throw new Error('path and email are required')
@@ -241,7 +240,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
241
240
 
242
241
  // Resolve role from role_slug param, or fall back to member_admin/member lookup
243
242
  const targetRoleSlug = role_slug || 'member'
244
- console.log('Looking for role:', targetRoleSlug)
245
243
 
246
244
  let { data: roles } = await adminDb
247
245
  .from('roles')
@@ -250,11 +248,9 @@ export const completeRegistration = createHandler(async (ctx, body) => {
250
248
  .limit(1)
251
249
 
252
250
  let role = roles?.[0]
253
- console.log('Found role:', role)
254
251
 
255
252
  // If specific role not found, try member_admin or member as fallback
256
253
  if (!role) {
257
- console.log('Role not found, trying fallbacks...')
258
254
  const { data: fallbackRoles } = await adminDb
259
255
  .from('roles')
260
256
  .select('id, slug')
@@ -262,11 +258,11 @@ export const completeRegistration = createHandler(async (ctx, body) => {
262
258
  .order('slug')
263
259
  .limit(1)
264
260
  role = fallbackRoles?.[0]
265
- console.log('Fallback role:', role)
266
261
  }
267
262
 
268
- // If no roles exist yet (apps not installed), create a default member role
269
- if (!role) {
263
+ // If no roles exist yet and no specific role was requested, create a default member role
264
+ // But only if we're not trying to assign a specific app role
265
+ if (!role && !role_slug) {
270
266
  const { data: newRole, error: roleErr } = await adminDb
271
267
  .from('roles')
272
268
  .insert({
@@ -53,7 +53,8 @@ interface RegistrationConfig {
53
53
  default_role: string
54
54
  allow_url_override: boolean
55
55
  apps: Record<string, {
56
- role: string
56
+ roles: string[]
57
+ default_role: string
57
58
  path: string
58
59
  enabled: boolean
59
60
  }>
@@ -237,7 +238,24 @@ export function RegisterPage() {
237
238
  // Determine role from URL params or config
238
239
  const urlRole = searchParams.get('role')
239
240
  const urlApp = searchParams.get('app')
240
- const effectiveRole = urlRole || (urlApp && regConfig?.apps[urlApp]?.role) || regConfig?.default_role || 'member'
241
+
242
+ let effectiveRole = regConfig?.default_role || 'member'
243
+
244
+ // If app is specified, use app's default role
245
+ if (urlApp && regConfig?.apps[urlApp]) {
246
+ const appConfig = regConfig.apps[urlApp]
247
+
248
+ // If URL role is provided, validate it against app's available roles
249
+ if (urlRole && appConfig.roles.includes(urlRole)) {
250
+ effectiveRole = urlRole
251
+ } else {
252
+ effectiveRole = appConfig.default_role
253
+ }
254
+ }
255
+ // If only URL role is specified (no app), use it directly
256
+ else if (urlRole) {
257
+ effectiveRole = urlRole
258
+ }
241
259
 
242
260
  if (domainMatchChoice === 'join') {
243
261
  // Join existing account as access_request
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-framework",
3
- "version": "0.3.59",
3
+ "version": "0.3.61",
4
4
  "description": "Multi-tenant, modular application platform for modern SaaS systems",
5
5
  "type": "module",
6
6
  "bin": {
package/spine.config.json CHANGED
@@ -6,12 +6,14 @@
6
6
  "allow_url_override": true,
7
7
  "apps": {
8
8
  "portal": {
9
- "role": "member",
9
+ "roles": ["member"],
10
+ "default_role": "member",
10
11
  "path": "/portal",
11
12
  "enabled": true
12
13
  },
13
14
  "cortex": {
14
- "role": "support",
15
+ "roles": ["support", "support_admin"],
16
+ "default_role": "support",
15
17
  "path": "/cortex",
16
18
  "enabled": true
17
19
  }