@fonderie/workspaces 1.2.1 → 2.0.0

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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/routes.ts","../src/schemas.ts","../src/middlewares/workspace-context.ts","../src/services/members.ts","../src/services/workspaces.ts","../src/controllers/workspace.controller.ts","../src/models/workspace.model.ts","../src/models/member.model.ts","../src/services/roles.ts","../src/models/role.model.ts","../src/dtos/workspace.ts","../src/controllers/member.controller.ts","../src/controllers/role.controller.ts","../src/controllers/invitation.controller.ts","../src/services/invitations.ts","../src/models/invitation.model.ts","../src/module.ts","../src/middlewares/require-workspace.ts"],"sourcesContent":["export const MESSAGE_KEYS = {\n\tworkspaceInvitation: 'workspace-invitation',\n} as const;\n\nexport type WorkspacesMessageKey = (typeof MESSAGE_KEYS)[keyof typeof MESSAGE_KEYS];\n\nexport const EVENT_KEYS = {\n\tpersonalWorkspaceCreated: 'fonderie.workspace.personal.created',\n} as const;\n\nexport type WorkspacesEventKey = (typeof EVENT_KEYS)[keyof typeof EVENT_KEYS];\n\nexport interface IWorkspacesConfig {\n\t// How long invitations are valid. Default: '7d'\n\tinvitationTtl?: string;\n\n\t// Auto-create a personal workspace when user.registered fires.\n\t// Requires an EventBus to be passed to WorkspacesModule. Default: true\n\tpersonalWorkspace?: boolean;\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { Middleware } from '@fonderie/core';\nimport type { EventBus } from '@fonderie/events';\nimport { requireAuth, validate } from '@fonderie/core/middlewares';\n\nimport {\n\tcreateRoleSchema,\n\tupdateRoleSchema,\n\taddMemberRoleSchema,\n\tupdateSettingsSchema,\n\tcreateWorkspaceSchema,\n\tupdateWorkspaceSchema,\n\tacceptInvitationSchema,\n\tcreateInvitationsSchema,\n\tsetRolePermissionsSchema,\n} from './schemas';\n\nimport type { IWorkspacesConfig } from './config';\nimport { withWorkspace } from './middlewares/workspace-context';\n\nimport { workspaceController } from './controllers/workspace.controller';\nimport { memberController } from './controllers/member.controller';\nimport { roleController } from './controllers/role.controller';\nimport { invitationController } from './controllers/invitation.controller';\n\ntype RouteDefinition = [string, string, ...Middleware[]];\n\nexport function buildWorkspaceRoutes(\n\tstore: IStoreAdapter,\n\tconfig: IWorkspacesConfig,\n\tbus?: EventBus,\n): RouteDefinition[] {\n\tconst ttl = config.invitationTtl ?? '7d';\n\tconst wsCtx = withWorkspace(store);\n\n\tconst workspace = workspaceController(store, config);\n\tconst member = memberController(store);\n\tconst role = roleController(store);\n\tconst invitation = invitationController(store, ttl, bus);\n\n\treturn [\n\t\t// ── Workspace creation + listing (no workspace context required)\n\t\t['POST', '/workspaces', requireAuth, validate(createWorkspaceSchema), workspace.create],\n\t\t['GET', '/workspaces', requireAuth, workspace.list],\n\n\t\t// ── Members (workspace resolved from X-Workspace-ID header)\n\t\t['GET', '/workspaces/members', requireAuth, wsCtx, member.list],\n\t\t['DELETE', '/workspaces/members/:userId', requireAuth, wsCtx, member.remove],\n\t\t['GET', '/workspaces/members/:userId/roles', requireAuth, wsCtx, member.getUserRoles],\n\t\t['POST', '/workspaces/members/:userId/roles', requireAuth, wsCtx, validate(addMemberRoleSchema), member.addRole],\n\t\t['DELETE', '/workspaces/members/:userId/roles/:roleId', requireAuth, wsCtx, member.removeRole],\n\n\t\t// ── Invitations\n\t\t['GET', '/workspaces/invitations', requireAuth, wsCtx, invitation.list],\n\t\t['POST', '/workspaces/invitations', requireAuth, wsCtx, validate(createInvitationsSchema), invitation.invite],\n\t\t['DELETE', '/workspaces/invitations/:inviteId', requireAuth, wsCtx, invitation.cancel],\n\t\t['POST', '/workspaces/invitations/accept', requireAuth, validate(acceptInvitationSchema), invitation.accept],\n\n\t\t// ── Roles\n\t\t['POST', '/workspaces/roles', requireAuth, wsCtx, validate(createRoleSchema), role.create],\n\t\t['GET', '/workspaces/roles', requireAuth, wsCtx, role.list],\n\t\t['GET', '/workspaces/roles/:roleId', requireAuth, wsCtx, role.get],\n\t\t['PUT', '/workspaces/roles/:roleId', requireAuth, wsCtx, validate(updateRoleSchema), role.update],\n\t\t['DELETE', '/workspaces/roles/:roleId', requireAuth, wsCtx, role.remove],\n\t\t['POST', '/workspaces/roles/:roleId/permissions', requireAuth, wsCtx, validate(setRolePermissionsSchema), role.setPermissions],\n\n\t\t// ── Workspace lifecycle\n\t\t['POST', '/workspaces/archive', requireAuth, wsCtx, workspace.archive],\n\t\t['POST', '/workspaces/restore', requireAuth, wsCtx, workspace.restore],\n\t\t['GET', '/workspaces/settings', requireAuth, wsCtx, workspace.getSettings],\n\t\t['PUT', '/workspaces/settings', requireAuth, wsCtx, validate(updateSettingsSchema), workspace.updateSettings],\n\n\t\t// ── Path-based lookup by ID (admin / cross-workspace use)\n\t\t['GET', '/workspaces/:id', requireAuth, wsCtx, workspace.get],\n\n\t\t// ── Update current workspace — ID resolved from X-Workspace-ID header\n\t\t// (or personal workspace fallback when header is absent)\n\t\t['PUT', '/workspaces', requireAuth, wsCtx, validate(updateWorkspaceSchema), workspace.update],\n\t];\n}\n","import { z } from 'zod';\n\n// Request schemas — the validation contract for every body-taking workspaces\n// route. Wired into routes.ts via @fonderie/core's validate(); exported so\n// docs generators and typed clients read the source of truth the runtime\n// enforces. Same pattern as @fonderie/auth/src/schemas.ts.\n\nconst name = z.string().trim().min(1, 'name is required').max(200);\nconst email = z.string().trim().pipe(z.email());\n\nexport const createWorkspaceSchema = z.object({\n\tname,\n\tdescription: z.string().max(2000).optional(),\n\t// PERSONAL workspaces are created automatically by the module; the\n\t// controller enforces the rule — schema just types the field.\n\ttype: z.string().max(40).optional(),\n});\n\nconst addressSchema = z\n\t.object({\n\t\tline1: z.string().max(200).optional(),\n\t\tline2: z.string().max(200).optional(),\n\t\tcity: z.string().max(100).optional(),\n\t\tregion: z.string().max(100).optional(),\n\t\tpostalCode: z.string().max(20).optional(),\n\t\tcountry: z.string().max(60).optional(),\n\t})\n\t.passthrough();\n\nexport const updateWorkspaceSchema = z\n\t.object({\n\t\tname: z.string().trim().min(1).max(200).optional(),\n\t\tdescription: z.string().max(2000).nullable().optional(),\n\t\tmotto: z.string().max(300).nullable().optional(),\n\t\tphone: z.string().max(32).nullable().optional(),\n\t\tbusinessType: z.string().max(100).nullable().optional(),\n\t\taddress: addressSchema.nullable().optional(),\n\t})\n\t.refine((o) => Object.values(o).some((v) => v !== undefined), 'Provide at least one field');\n\nexport const updateSettingsSchema = z\n\t.object({\n\t\tlocale: z.string().max(35).optional(),\n\t\ttimezone: z.string().max(64).optional(),\n\t\tcurrency: z.string().max(8).optional(),\n\t\tdateFormat: z.string().max(32).optional(),\n\t\ttimeFormat: z.string().max(32).optional(),\n\t})\n\t.refine((o) => Object.values(o).some((v) => v !== undefined), 'No settings provided');\n\nexport const addMemberRoleSchema = z.object({ roleId: z.string().min(1, 'roleId is required') });\n\nconst inviteEntry = z.object({\n\temail,\n\troleId: z.string().min(1).optional(),\n});\n\n// POST /workspaces/invitations accepts a single invite or a batch.\nexport const createInvitationsSchema = z.union([\n\tinviteEntry,\n\tz.array(inviteEntry).min(1, 'at least one invite is required'),\n]);\n\nexport const acceptInvitationSchema = z.object({ pin: z.string().trim().min(1, 'pin is required') });\n\nexport const createRoleSchema = z.object({\n\tname,\n\tdescription: z.string().max(1000).optional(),\n});\n\nexport const updateRoleSchema = z\n\t.object({\n\t\tname: z.string().trim().min(1).max(200).optional(),\n\t\tdescription: z.string().max(1000).nullable().optional(),\n\t\tactive: z.boolean().optional(),\n\t})\n\t.refine((o) => Object.values(o).some((v) => v !== undefined), 'Provide at least one field');\n\nexport const setRolePermissionsSchema = z.object({\n\tpermissions: z.array(\n\t\tz.object({\n\t\t\tpermissionKey: z.string().min(1),\n\t\t\tcanCreate: z.boolean().optional(),\n\t\t\tcanRead: z.boolean().optional(),\n\t\t\tcanUpdate: z.boolean().optional(),\n\t\t\tcanDelete: z.boolean().optional(),\n\t\t}),\n\t),\n});\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { Middleware } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { getMember } from '../services/members';\nimport { findWorkspaceById, findPersonalWorkspace } from '../services/workspaces';\n\n// Resolves ctx.workspace from:\n// 1. Route param :workspaceId or :id (path-based admin routes)\n// 2. X-Workspace-ID request header (standard resource routes)\n// Validates the current user is an active member.\n// Must run after withSession.\n\nfunction makeHandler(store: IStoreAdapter): Middleware {\n\treturn async (ctx, next) => {\n\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\tconst workspaceId =\n\t\t\tparams?.['workspaceId'] ??\n\t\t\tparams?.['id'] ??\n\t\t\tctx.request.headers.get('x-workspace-id') ??\n\t\t\tundefined;\n\n\t\tif (!workspaceId) {\n\t\t\t// DMZ fallback — if the caller didn't specify a workspace, use their personal one.\n\t\t\t// No membership check needed: the user is always the sole owner.\n\t\t\tif (ctx.user) {\n\t\t\t\tconst personal = await findPersonalWorkspace(ctx.user.id, store);\n\t\t\t\tif (personal) Object.assign(ctx, { workspace: personal });\n\t\t\t}\n\t\t\treturn next();\n\t\t}\n\n\t\tconst workspace = await findWorkspaceById(workspaceId, store);\n\t\tif (!workspace) {\n\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t}\n\n\t\tif (ctx.user) {\n\t\t\tconst member = await getMember(ctx.user.id, workspaceId, store);\n\t\t\tif (!member) {\n\t\t\t\treturn setApiResponse(HTTP.FORBIDDEN, 'FORBIDDEN', 'Not a member of this workspace');\n\t\t\t}\n\t\t}\n\n\t\tObject.assign(ctx, { workspace });\n\t\treturn next();\n\t};\n}\n\nexport function withWorkspace(store: IStoreAdapter): Middleware;\nexport function withWorkspace(\n\tstore: IStoreAdapter,\n\tctx: IFonderieContext,\n\tnext: () => Promise<Response>,\n): Promise<Response>;\nexport function withWorkspace(\n\tstore: IStoreAdapter,\n\tctx?: IFonderieContext,\n\tnext?: () => Promise<Response>,\n): Middleware | Promise<Response> {\n\tconst handler = makeHandler(store);\n\tif (ctx !== undefined && next !== undefined) return handler(ctx, next);\n\treturn handler;\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IMember, IRole } from '../types';\n\nconst SELECT_MEMBER = `\n\truw.user_id AS \"userId\",\n\truw.workspace_id AS \"workspaceId\",\n\truw.role_id AS \"roleId\",\n\tr.name AS \"roleName\",\n\truw.confirmed AS \"confirmed\",\n\truw.created_at AS \"createdAt\",\n\tu.first_name AS \"firstName\",\n\tu.last_name AS \"lastName\",\n\tu.email AS \"email\",\n\tu.profile_image_url AS \"profileImageUrl\"\n`;\n\nexport async function getMember(\n\tuserId: string,\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<IMember | null> {\n\tconst [row] = await store.query<IMember>(\n\t\t`SELECT ${SELECT_MEMBER}\n\t\t FROM fonderie_role_user_workspaces ruw\n\t\t LEFT JOIN fonderie_roles r ON r.id = ruw.role_id\n\t\t LEFT JOIN fonderie_users u ON u.id = ruw.user_id\n\t\t WHERE ruw.user_id = $1\n\t\t AND ruw.workspace_id = $2\n\t\t AND ruw.removed = false\n\t\t AND ruw.suspended = false\n\t\t LIMIT 1`,\n\t\t[userId, workspaceId],\n\t);\n\treturn row ?? null;\n}\n\nexport async function listMembers(workspaceId: string, store: IStoreAdapter): Promise<IMember[]> {\n\treturn store.query<IMember>(\n\t\t`SELECT ${SELECT_MEMBER}\n\t\t FROM fonderie_role_user_workspaces ruw\n\t\t LEFT JOIN fonderie_roles r ON r.id = ruw.role_id\n\t\t LEFT JOIN fonderie_users u ON u.id = ruw.user_id\n\t\t WHERE ruw.workspace_id = $1\n\t\t AND ruw.removed = false\n\t\t AND ruw.suspended = false\n\t\t ORDER BY ruw.created_at ASC`,\n\t\t[workspaceId],\n\t);\n}\n\nexport async function addMember(\n\topts: { userId: string; workspaceId: string; roleId: string; confirmed?: boolean },\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`INSERT INTO fonderie_role_user_workspaces (user_id, workspace_id, role_id, confirmed)\n\t\t VALUES ($1, $2, $3, $4)\n\t\t ON CONFLICT (user_id, workspace_id, role_id) DO UPDATE\n\t\t SET confirmed = $4, removed = false, suspended = false`,\n\t\t[opts.userId, opts.workspaceId, opts.roleId, opts.confirmed ?? true],\n\t);\n}\n\nexport async function removeMember(\n\tuserId: string,\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`UPDATE fonderie_role_user_workspaces\n\t\t SET removed = true\n\t\t WHERE user_id = $1\n\t\t AND workspace_id = $2`,\n\t\t[userId, workspaceId],\n\t);\n}\n\nexport async function getUserRoles(\n\tuserId: string,\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<IRole[]> {\n\treturn store.query<IRole>(\n\t\t`SELECT\n\t\t r.id,\n\t\t r.name,\n\t\t r.is_system AS \"isSystem\",\n\t\t r.active,\n\t\t r.description,\n\t\t r.workspace_id AS \"workspaceId\"\n\t\t FROM fonderie_role_user_workspaces ruw\n\t\t JOIN fonderie_roles r ON r.id = ruw.role_id\n\t\t WHERE ruw.user_id = $1\n\t\t AND ruw.workspace_id = $2\n\t\t AND ruw.removed = false\n\t\t AND ruw.suspended = false`,\n\t\t[userId, workspaceId],\n\t);\n}\n\nexport async function addRoleToMember(\n\tuserId: string,\n\tworkspaceId: string,\n\troleId: string,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`INSERT INTO fonderie_role_user_workspaces (user_id, workspace_id, role_id, confirmed)\n\t\t VALUES ($1, $2, $3, true)\n\t\t ON CONFLICT (user_id, workspace_id, role_id) DO UPDATE\n\t\t SET confirmed = true, removed = false, suspended = false`,\n\t\t[userId, workspaceId, roleId],\n\t);\n}\n\nexport async function removeRoleFromMember(\n\tuserId: string,\n\tworkspaceId: string,\n\troleId: string,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tconst remaining = await store.query<{ count: string }>(\n\t\t`SELECT COUNT(*) AS count\n\t\t FROM fonderie_role_user_workspaces\n\t\t WHERE user_id = $1\n\t\t AND workspace_id = $2\n\t\t AND removed = false`,\n\t\t[userId, workspaceId],\n\t);\n\tconst count = parseInt(remaining[0]?.count ?? '0', 10);\n\tif (count <= 1) throw new Error('Cannot remove last role from member');\n\n\tawait store.query(\n\t\t`DELETE FROM fonderie_role_user_workspaces\n\t\t WHERE user_id = $1\n\t\t AND workspace_id = $2\n\t\t AND role_id = $3`,\n\t\t[userId, workspaceId, roleId],\n\t);\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWorkspace, IWorkspaceAddress, IWorkspaceSettings } from '../types';\n\nconst SELECT_WS = `\n\tid,\n\tname,\n\tslug,\n\ttype,\n\tdescription,\n\tmotto,\n\tphone,\n\tbusiness_type AS \"businessType\",\n\taddress,\n\tplan,\n\towner_id AS \"ownerId\",\n\tis_personal AS \"isPersonal\",\n\tarchived_at AS \"archivedAt\",\n\tarchived_by AS \"archivedBy\",\n\tcreated_at AS \"createdAt\",\n\tupdated_at AS \"updatedAt\"\n`;\n\nconst SELECT_WS_W = `\n\tw.id,\n\tw.name,\n\tw.slug,\n\tw.type,\n\tw.description,\n\tw.motto,\n\tw.phone,\n\tw.business_type AS \"businessType\",\n\tw.address,\n\tw.plan,\n\tw.owner_id AS \"ownerId\",\n\tw.is_personal AS \"isPersonal\",\n\tw.archived_at AS \"archivedAt\",\n\tw.archived_by AS \"archivedBy\",\n\tw.created_at AS \"createdAt\",\n\tw.updated_at AS \"updatedAt\"\n`;\n\nexport async function findWorkspaceById(\n\tid: string,\n\tstore: IStoreAdapter,\n): Promise<IWorkspace | null> {\n\tconst [row] = await store.query<IWorkspace>(\n\t\t`SELECT ${SELECT_WS} FROM fonderie_workspaces WHERE id = $1`,\n\t\t[id],\n\t);\n\treturn row ?? null;\n}\n\nexport async function findWorkspacesByUserId(\n\tuserId: string,\n\tstore: IStoreAdapter,\n): Promise<IWorkspace[]> {\n\treturn store.query<IWorkspace>(\n\t\t`SELECT ${SELECT_WS_W}\n\t\t FROM fonderie_workspaces w\n\t\t JOIN fonderie_role_user_workspaces ruw ON ruw.workspace_id = w.id\n\t\t WHERE ruw.user_id = $1\n\t\t AND ruw.removed = false\n\t\t AND ruw.suspended = false\n\t\t GROUP BY w.id\n\t\t ORDER BY w.created_at ASC`,\n\t\t[userId],\n\t);\n}\n\nexport async function createWorkspace(\n\topts: {\n\t\tname: string;\n\t\tslug: string;\n\t\townerId: string;\n\t\ttype?: string;\n\t\tdescription?: string;\n\t\tplan?: string;\n\t},\n\tstore: IStoreAdapter,\n): Promise<IWorkspace> {\n\tconst [workspace] = await store.query<IWorkspace>(\n\t\t`INSERT INTO fonderie_workspaces (name, slug, owner_id, type, description, plan)\n\t\t VALUES ($1, $2, $3, $4, $5, $6)\n\t\t RETURNING ${SELECT_WS}`,\n\t\t[\n\t\t\topts.name,\n\t\t\topts.slug,\n\t\t\topts.ownerId,\n\t\t\topts.type ?? 'ORGANIZATION',\n\t\t\topts.description ?? null,\n\t\t\topts.plan ?? 'free',\n\t\t],\n\t);\n\n\tif (!workspace) throw new Error('Failed to create workspace');\n\treturn workspace;\n}\n\n// Returns the new workspace, or null if the personal workspace already exists (idempotent).\nexport async function createPersonalWorkspace(\n\topts: { name: string; slug: string; ownerId: string },\n\tstore: IStoreAdapter,\n): Promise<IWorkspace | null> {\n\tconst [workspace] = await store.query<IWorkspace>(\n\t\t`INSERT INTO fonderie_workspaces (name, slug, owner_id, type, is_personal)\n\t\t VALUES ($1, $2, $3, 'PERSONAL', true)\n\t\t ON CONFLICT (owner_id) WHERE is_personal = true DO NOTHING\n\t\t RETURNING ${SELECT_WS}`,\n\t\t[opts.name, opts.slug, opts.ownerId],\n\t);\n\treturn workspace ?? null;\n}\n\nexport async function findPersonalWorkspace(\n\tuserId: string,\n\tstore: IStoreAdapter,\n): Promise<IWorkspace | null> {\n\tconst [row] = await store.query<IWorkspace>(\n\t\t`SELECT ${SELECT_WS} FROM fonderie_workspaces\n\t\t WHERE owner_id = $1 AND is_personal = true\n\t\t LIMIT 1`,\n\t\t[userId],\n\t);\n\treturn row ?? null;\n}\n\nexport async function updateWorkspace(\n\tid: string,\n\topts: {\n\t\tname?: string;\n\t\tdescription?: string | null;\n\t\tslug?: string;\n\t\tmotto?: string | null;\n\t\tphone?: string | null;\n\t\tbusinessType?: string | null;\n\t\taddress?: IWorkspaceAddress | null;\n\t},\n\tstore: IStoreAdapter,\n): Promise<IWorkspace | null> {\n\tconst sets: string[] = ['updated_at = now()'];\n\tconst params: unknown[] = [id];\n\n\tif (opts.name !== undefined) {\n\t\tparams.push(opts.name);\n\t\tsets.push(`name = $${params.length}`);\n\t}\n\tif (opts.description !== undefined) {\n\t\tparams.push(opts.description);\n\t\tsets.push(`description = $${params.length}`);\n\t}\n\tif (opts.slug !== undefined) {\n\t\tparams.push(opts.slug);\n\t\tsets.push(`slug = $${params.length}`);\n\t}\n\tif (opts.motto !== undefined) {\n\t\tparams.push(opts.motto);\n\t\tsets.push(`motto = $${params.length}`);\n\t}\n\tif (opts.phone !== undefined) {\n\t\tparams.push(opts.phone);\n\t\tsets.push(`phone = $${params.length}`);\n\t}\n\tif (opts.businessType !== undefined) {\n\t\tparams.push(opts.businessType);\n\t\tsets.push(`business_type = $${params.length}`);\n\t}\n\tif (opts.address !== undefined) {\n\t\tparams.push(JSON.stringify(opts.address ?? {}));\n\t\tsets.push(`address = $${params.length}::jsonb`);\n\t}\n\n\tconst [row] = await store.query<IWorkspace>(\n\t\t`UPDATE fonderie_workspaces\n\t\t SET ${sets.join(', ')}\n\t\t WHERE id = $1\n\t\t RETURNING ${SELECT_WS}`,\n\t\tparams,\n\t);\n\treturn row ?? null;\n}\n\nexport async function archiveWorkspace(\n\tid: string,\n\tbyUser: string,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`UPDATE fonderie_workspaces\n\t\t SET archived_at = now(), archived_by = $2, updated_at = now()\n\t\t WHERE id = $1 AND archived_at IS NULL`,\n\t\t[id, byUser],\n\t);\n}\n\nexport async function restoreWorkspace(id: string, store: IStoreAdapter): Promise<void> {\n\tawait store.query(\n\t\t`UPDATE fonderie_workspaces\n\t\t SET archived_at = NULL, archived_by = NULL, updated_at = now()\n\t\t WHERE id = $1`,\n\t\t[id],\n\t);\n}\n\nconst SETTINGS_DEFAULTS: IWorkspaceSettings = {\n\tlocale: 'en-US',\n\ttimezone: 'UTC',\n\tcurrency: 'USD',\n\tdateFormat: 'MM/DD/YYYY',\n\ttimeFormat: 'hh:mm A',\n};\n\nexport async function getWorkspaceSettings(\n\tid: string,\n\tstore: IStoreAdapter,\n): Promise<IWorkspaceSettings> {\n\tconst [row] = await store.query<{ settings: Record<string, unknown> }>(\n\t\t`SELECT settings FROM fonderie_workspaces WHERE id = $1`,\n\t\t[id],\n\t);\n\tconst raw = row?.settings ?? {};\n\tconst s = (raw['settings'] as Record<string, unknown> | undefined) ?? raw;\n\n\treturn {\n\t\tlocale: typeof s['locale'] === 'string' ? s['locale'] : SETTINGS_DEFAULTS.locale,\n\t\ttimezone: typeof s['timezone'] === 'string' ? s['timezone'] : SETTINGS_DEFAULTS.timezone,\n\t\tcurrency: typeof s['currency'] === 'string' ? s['currency'] : SETTINGS_DEFAULTS.currency,\n\t\tdateFormat:\n\t\t\ttypeof s['dateFormat'] === 'string' ? s['dateFormat'] : SETTINGS_DEFAULTS.dateFormat,\n\t\ttimeFormat:\n\t\t\ttypeof s['timeFormat'] === 'string' ? s['timeFormat'] : SETTINGS_DEFAULTS.timeFormat,\n\t};\n}\n\nexport async function updateWorkspaceSettings(\n\tid: string,\n\tsettings: Partial<IWorkspaceSettings>,\n\tstore: IStoreAdapter,\n): Promise<IWorkspaceSettings> {\n\tawait store.query(\n\t\t`UPDATE fonderie_workspaces\n\t\t SET settings = settings || jsonb_build_object('settings', $2::jsonb),\n\t\t updated_at = now()\n\t\t WHERE id = $1`,\n\t\t[id, JSON.stringify(settings)],\n\t);\n\treturn getWorkspaceSettings(id, store);\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWorkspacesConfig } from '../config';\nimport type { IWorkspace } from '../types';\nimport { WorkspaceModel } from '../models/workspace.model';\nimport { MemberModel } from '../models/member.model';\nimport { RoleModel } from '../models/role.model';\nimport { toWorkspaceDTO, toSettingsDTO } from '../dtos/workspace';\n\nexport function workspaceController(store: IStoreAdapter, config: IWorkspacesConfig) {\n\tconst workspaces = new WorkspaceModel(store);\n\tconst members = new MemberModel(store);\n\tconst roles = new RoleModel(store);\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.user)\n\t\t\t\treturn setApiResponse(HTTP.UNAUTHORIZED, 'UNAUTHORIZED', 'Authentication required');\n\t\t\tconst list = await workspaces.findByUserId(ctx.user.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'WORKSPACES_FETCHED', 'Workspaces retrieved successfully.', {\n\t\t\t\tworkspaces: list.map(toWorkspaceDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync create(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst name = body?.['name'];\n\t\t\tconst description = body?.['description'];\n\t\t\tconst type = body?.['type'];\n\n\t\t\tif (typeof name !== 'string' || name.trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'name is required');\n\t\t\t}\n\n\t\t\tif (type === 'PERSONAL') {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t'Personal workspaces are created automatically',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst slug = name\n\t\t\t\t.toLowerCase()\n\t\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t\t.replace(/^-|-$/g, '');\n\n\t\t\tconst workspace = await store.transaction(async (tx) => {\n\t\t\t\tconst wsOpts: Parameters<typeof workspaces.create>[0] = {\n\t\t\t\t\tname: name.trim(),\n\t\t\t\t\tslug,\n\t\t\t\t\townerId: ctx.user!.id,\n\t\t\t\t\ttype: typeof type === 'string' ? type : 'ORGANIZATION',\n\t\t\t\t};\n\t\t\t\tif (typeof description === 'string') wsOpts.description = description;\n\n\t\t\t\tconst wsModel = new WorkspaceModel(tx);\n\t\t\t\tconst roleModel = new RoleModel(tx);\n\t\t\t\tconst memModel = new MemberModel(tx);\n\n\t\t\t\tconst ws = await wsModel.create(wsOpts);\n\n\t\t\t\tconst adminRole = await roleModel.findSystem('ADMIN');\n\t\t\t\tif (!adminRole) throw new Error('System ADMIN role not found');\n\t\t\t\tawait memModel.add({ userId: ctx.user!.id, workspaceId: ws.id, roleId: adminRole.id });\n\n\t\t\t\treturn ws;\n\t\t\t});\n\n\t\t\treturn setApiResponse(HTTP.CREATED, 'WORKSPACE_CREATED', 'Workspace created successfully.', {\n\t\t\t\tworkspace: toWorkspaceDTO(workspace),\n\t\t\t});\n\t\t},\n\n\t\tasync get(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\treturn setApiResponse(HTTP.OK, 'WORKSPACE_FETCHED', 'Workspace retrieved successfully.', {\n\t\t\t\tworkspace: toWorkspaceDTO(ctx.workspace as IWorkspace),\n\t\t\t});\n\t\t},\n\n\t\tasync update(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst opts: Parameters<typeof workspaces.update>[1] = {};\n\n\t\t\tif (typeof body?.['name'] === 'string') opts.name = body['name'].trim();\n\t\t\tif (body?.['description'] !== undefined)\n\t\t\t\topts.description = typeof body['description'] === 'string' ? body['description'] : null;\n\t\t\tif (body?.['motto'] !== undefined)\n\t\t\t\topts.motto = typeof body['motto'] === 'string' ? body['motto'] : null;\n\t\t\tif (body?.['phone'] !== undefined)\n\t\t\t\topts.phone = typeof body['phone'] === 'string' ? body['phone'].trim() : null;\n\t\t\tif (body?.['businessType'] !== undefined)\n\t\t\t\topts.businessType = typeof body['businessType'] === 'string' ? body['businessType'] : null;\n\t\t\tif (body?.['address'] !== undefined && typeof body['address'] === 'object')\n\t\t\t\topts.address = (body['address'] ?? null) as NonNullable<\n\t\t\t\t\tParameters<typeof workspaces.update>[1]\n\t\t\t\t>['address'] & (object | null);\n\n\t\t\tconst workspace = await workspaces.update(ctx.workspace.id, opts);\n\t\t\tif (!workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\treturn setApiResponse(HTTP.OK, 'WORKSPACE_UPDATED', 'Workspace updated successfully.', {\n\t\t\t\tworkspace: toWorkspaceDTO(workspace),\n\t\t\t});\n\t\t},\n\n\t\tasync archive(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\tif (ctx.workspace.isPersonal) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.FORBIDDEN,\n\t\t\t\t\t'FORBIDDEN',\n\t\t\t\t\t'Personal workspaces cannot be archived',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait workspaces.archive(ctx.workspace.id, ctx.user!.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'WORKSPACE_ARCHIVED', 'Workspace archived successfully.');\n\t\t},\n\n\t\tasync restore(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tawait workspaces.restore(ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'WORKSPACE_RESTORED', 'Workspace restored successfully.');\n\t\t},\n\n\t\tasync getSettings(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst settings = await workspaces.getSettings(ctx.workspace.id);\n\t\t\treturn setApiResponse(\n\t\t\t\tHTTP.OK,\n\t\t\t\t'SETTINGS_FETCHED',\n\t\t\t\t'Workspace settings retrieved successfully.',\n\t\t\t\t{\n\t\t\t\t\tsettings: toSettingsDTO(settings),\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\n\t\tasync updateSettings(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tif (!body || Object.keys(body).length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'No settings provided');\n\t\t\t}\n\n\t\t\tconst patch: Record<string, string> = {};\n\t\t\tfor (const key of ['locale', 'timezone', 'currency', 'dateFormat', 'timeFormat'] as const) {\n\t\t\t\tif (typeof body[key] === 'string') patch[key] = body[key] as string;\n\t\t\t}\n\n\t\t\tconst settings = await workspaces.updateSettings(ctx.workspace.id, patch);\n\t\t\treturn setApiResponse(\n\t\t\t\tHTTP.OK,\n\t\t\t\t'SETTINGS_UPDATED',\n\t\t\t\t'Workspace settings updated successfully.',\n\t\t\t\t{\n\t\t\t\t\tsettings: toSettingsDTO(settings),\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t};\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWorkspace, IWorkspaceSettings } from '../types';\nimport {\n\tfindWorkspaceById,\n\tfindWorkspacesByUserId,\n\tcreateWorkspace,\n\tcreatePersonalWorkspace,\n\tfindPersonalWorkspace,\n\tupdateWorkspace,\n\tarchiveWorkspace,\n\trestoreWorkspace,\n\tgetWorkspaceSettings,\n\tupdateWorkspaceSettings,\n} from '../services/workspaces';\n\nexport class WorkspaceModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tfindById(id: string): Promise<IWorkspace | null> {\n\t\treturn findWorkspaceById(id, this.store);\n\t}\n\n\tfindByUserId(userId: string): Promise<IWorkspace[]> {\n\t\treturn findWorkspacesByUserId(userId, this.store);\n\t}\n\n\tcreate(opts: Parameters<typeof createWorkspace>[0]): Promise<IWorkspace> {\n\t\treturn createWorkspace(opts, this.store);\n\t}\n\n\tcreatePersonal(opts: Parameters<typeof createPersonalWorkspace>[0]): Promise<IWorkspace | null> {\n\t\treturn createPersonalWorkspace(opts, this.store);\n\t}\n\n\tfindPersonal(userId: string): Promise<IWorkspace | null> {\n\t\treturn findPersonalWorkspace(userId, this.store);\n\t}\n\n\tupdate(id: string, opts: Parameters<typeof updateWorkspace>[1]): Promise<IWorkspace | null> {\n\t\treturn updateWorkspace(id, opts, this.store);\n\t}\n\n\tarchive(id: string, byUser: string): Promise<void> {\n\t\treturn archiveWorkspace(id, byUser, this.store);\n\t}\n\n\trestore(id: string): Promise<void> {\n\t\treturn restoreWorkspace(id, this.store);\n\t}\n\n\tgetSettings(id: string): Promise<IWorkspaceSettings> {\n\t\treturn getWorkspaceSettings(id, this.store);\n\t}\n\n\tupdateSettings(id: string, settings: Partial<IWorkspaceSettings>): Promise<IWorkspaceSettings> {\n\t\treturn updateWorkspaceSettings(id, settings, this.store);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IMember, IRole } from '../types';\nimport {\n\tgetMember,\n\tlistMembers,\n\taddMember,\n\tremoveMember,\n\tgetUserRoles,\n\taddRoleToMember,\n\tremoveRoleFromMember,\n} from '../services/members';\n\nexport class MemberModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tget(userId: string, workspaceId: string): Promise<IMember | null> {\n\t\treturn getMember(userId, workspaceId, this.store);\n\t}\n\n\tlist(workspaceId: string): Promise<IMember[]> {\n\t\treturn listMembers(workspaceId, this.store);\n\t}\n\n\tadd(opts: Parameters<typeof addMember>[0]): Promise<void> {\n\t\treturn addMember(opts, this.store);\n\t}\n\n\tremove(userId: string, workspaceId: string): Promise<void> {\n\t\treturn removeMember(userId, workspaceId, this.store);\n\t}\n\n\tgetUserRoles(userId: string, workspaceId: string): Promise<IRole[]> {\n\t\treturn getUserRoles(userId, workspaceId, this.store);\n\t}\n\n\taddRole(userId: string, workspaceId: string, roleId: string): Promise<void> {\n\t\treturn addRoleToMember(userId, workspaceId, roleId, this.store);\n\t}\n\n\tremoveRole(userId: string, workspaceId: string, roleId: string): Promise<void> {\n\t\treturn removeRoleFromMember(userId, workspaceId, roleId, this.store);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IRole } from '../types';\n\nconst SELECT_ROLE = `\n\tid,\n\tname,\n\tis_system AS \"isSystem\",\n\tactive,\n\tdescription,\n\tworkspace_id AS \"workspaceId\"\n`;\n\nexport async function createRole(\n\topts: { name: string; workspaceId: string; description?: string },\n\tstore: IStoreAdapter,\n): Promise<IRole> {\n\tconst [role] = await store.query<IRole>(\n\t\t`INSERT INTO fonderie_roles (name, workspace_id, description)\n\t\t VALUES ($1, $2, $3)\n\t\t RETURNING ${SELECT_ROLE}`,\n\t\t[opts.name, opts.workspaceId, opts.description ?? null],\n\t);\n\tif (!role) throw new Error('Failed to create role');\n\treturn role;\n}\n\nexport async function findSystemRole(name: string, store: IStoreAdapter): Promise<IRole | null> {\n\tconst [row] = await store.query<IRole>(\n\t\t`SELECT ${SELECT_ROLE} FROM fonderie_roles WHERE name = $1 AND is_system = true LIMIT 1`,\n\t\t[name],\n\t);\n\treturn row ?? null;\n}\n\nexport async function getRoleById(id: string, store: IStoreAdapter): Promise<IRole | null> {\n\tconst [row] = await store.query<IRole>(\n\t\t`SELECT ${SELECT_ROLE} FROM fonderie_roles WHERE id = $1`,\n\t\t[id],\n\t);\n\treturn row ?? null;\n}\n\nexport async function listWorkspaceRoles(\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<IRole[]> {\n\treturn store.query<IRole>(\n\t\t`SELECT ${SELECT_ROLE}\n\t\t FROM fonderie_roles\n\t\t WHERE workspace_id = $1 OR is_system = true\n\t\t ORDER BY is_system DESC, name ASC`,\n\t\t[workspaceId],\n\t);\n}\n\nexport async function updateRole(\n\tid: string,\n\topts: { name?: string; description?: string | null; active?: boolean },\n\tstore: IStoreAdapter,\n): Promise<IRole | null> {\n\tconst sets: string[] = [];\n\tconst params: unknown[] = [id];\n\n\tif (opts.name !== undefined) {\n\t\tparams.push(opts.name);\n\t\tsets.push(`name = $${params.length}`);\n\t}\n\tif (opts.description !== undefined) {\n\t\tparams.push(opts.description);\n\t\tsets.push(`description = $${params.length}`);\n\t}\n\tif (opts.active !== undefined) {\n\t\tparams.push(opts.active);\n\t\tsets.push(`active = $${params.length}`);\n\t}\n\n\tif (sets.length === 0) return getRoleById(id, store);\n\n\tconst [row] = await store.query<IRole>(\n\t\t`UPDATE fonderie_roles\n\t\t SET ${sets.join(', ')}\n\t\t WHERE id = $1 AND is_system = false\n\t\t RETURNING ${SELECT_ROLE}`,\n\t\tparams,\n\t);\n\treturn row ?? null;\n}\n\nexport async function deleteRole(\n\tid: string,\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`DELETE FROM fonderie_roles\n\t\t WHERE id = $1 AND workspace_id = $2 AND is_system = false`,\n\t\t[id, workspaceId],\n\t);\n}\n\nexport async function setRolePermissions(\n\troleId: string,\n\tworkspaceId: string,\n\tpermissions: Array<{\n\t\tpermissionKey: string;\n\t\tcanCreate: boolean;\n\t\tcanRead: boolean;\n\t\tcanUpdate: boolean;\n\t\tcanDelete: boolean;\n\t}>,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tif (permissions.length === 0) {\n\t\tawait store.query(\n\t\t\t`DELETE FROM fonderie_role_permissions WHERE role_id = $1 AND workspace_id = $2`,\n\t\t\t[roleId, workspaceId],\n\t\t);\n\t\treturn;\n\t}\n\n\tawait store.transaction(async (tx) => {\n\t\tawait tx.query(\n\t\t\t`DELETE FROM fonderie_role_permissions WHERE role_id = $1 AND workspace_id = $2`,\n\t\t\t[roleId, workspaceId],\n\t\t);\n\n\t\tfor (const p of permissions) {\n\t\t\tawait tx.query(\n\t\t\t\t`INSERT INTO fonderie_role_permissions\n\t\t\t\t (role_id, workspace_id, permission_key, can_create, can_read, can_update, can_delete)\n\t\t\t\t VALUES ($1, $2, $3, $4, $5, $6, $7)\n\t\t\t\t ON CONFLICT (role_id, permission_key)\n\t\t\t\t DO UPDATE SET\n\t\t\t\t can_create = $4, can_read = $5,\n\t\t\t\t can_update = $6, can_delete = $7`,\n\t\t\t\t[roleId, workspaceId, p.permissionKey, p.canCreate, p.canRead, p.canUpdate, p.canDelete],\n\t\t\t);\n\t\t}\n\t});\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IRole } from '../types';\nimport {\n\tcreateRole,\n\tfindSystemRole,\n\tgetRoleById,\n\tlistWorkspaceRoles,\n\tupdateRole,\n\tdeleteRole,\n\tsetRolePermissions,\n} from '../services/roles';\n\nexport class RoleModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tcreate(opts: Parameters<typeof createRole>[0]): Promise<IRole> {\n\t\treturn createRole(opts, this.store);\n\t}\n\n\tfindSystem(name: string): Promise<IRole | null> {\n\t\treturn findSystemRole(name, this.store);\n\t}\n\n\tfindById(id: string): Promise<IRole | null> {\n\t\treturn getRoleById(id, this.store);\n\t}\n\n\tlist(workspaceId: string): Promise<IRole[]> {\n\t\treturn listWorkspaceRoles(workspaceId, this.store);\n\t}\n\n\tupdate(id: string, opts: Parameters<typeof updateRole>[1]): Promise<IRole | null> {\n\t\treturn updateRole(id, opts, this.store);\n\t}\n\n\tdelete(id: string, workspaceId: string): Promise<void> {\n\t\treturn deleteRole(id, workspaceId, this.store);\n\t}\n\n\tsetPermissions(\n\t\troleId: string,\n\t\tworkspaceId: string,\n\t\tpermissions: Parameters<typeof setRolePermissions>[2],\n\t): Promise<void> {\n\t\treturn setRolePermissions(roleId, workspaceId, permissions, this.store);\n\t}\n}\n","import { stringOrEmpty, booleanOrFalse } from '@fonderie/core/parser';\n\nimport type { IWorkspace, IRole, IMember, IInvitation, IWorkspaceSettings } from '../types';\n\nexport interface IWorkspaceAddressDTO {\n\tline1: string;\n\tline2: string;\n\tcity: string;\n\tstate: string;\n\tzip: string;\n\tcountry: string;\n}\n\nexport interface IWorkspaceDTO {\n\tid: string;\n\tname: string;\n\tslug: string;\n\ttype: string;\n\tdescription: string;\n\tmotto: string;\n\tphone: string;\n\tbusinessType: string;\n\taddress: IWorkspaceAddressDTO;\n\tplan: string;\n\townerId: string;\n\tisPersonal: boolean;\n\tisArchived: boolean;\n\tarchivedAt: string;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface IRoleDTO {\n\tid: string;\n\tname: string;\n\tisSystem: boolean;\n\tactive: boolean;\n\tdescription: string;\n\tworkspaceId: string;\n}\n\nexport interface IMemberDTO {\n\tuserId: string;\n\tworkspaceId: string;\n\troleId: string;\n\troleName: string;\n\tconfirmed: boolean;\n\tcreatedAt: string;\n}\n\nexport interface IInvitationDTO {\n\tid: string;\n\tworkspaceId: string;\n\temail: string;\n\troleId: string;\n\ttoken: string;\n\tstatus: string;\n\texpiresAt: string;\n\tcreatedAt: string;\n}\n\nexport interface IWorkspaceSettingsDTO {\n\tlocale: string;\n\ttimezone: string;\n\tcurrency: string;\n\tdateFormat: string;\n\ttimeFormat: string;\n}\n\nexport function toWorkspaceDTO(ws: IWorkspace): IWorkspaceDTO {\n\tconst addr = ws.address ?? {};\n\treturn {\n\t\tid: stringOrEmpty(ws.id),\n\t\tname: stringOrEmpty(ws.name),\n\t\tslug: stringOrEmpty(ws.slug),\n\t\ttype: stringOrEmpty(ws.type),\n\t\tdescription: stringOrEmpty(ws.description),\n\t\tmotto: stringOrEmpty(ws.motto),\n\t\tphone: stringOrEmpty(ws.phone),\n\t\tbusinessType: stringOrEmpty(ws.businessType),\n\t\taddress: {\n\t\t\tline1: stringOrEmpty(addr.line1),\n\t\t\tline2: stringOrEmpty(addr.line2),\n\t\t\tcity: stringOrEmpty(addr.city),\n\t\t\tstate: stringOrEmpty(addr.state),\n\t\t\tzip: stringOrEmpty(addr.zip),\n\t\t\tcountry: stringOrEmpty(addr.country),\n\t\t},\n\t\tplan: stringOrEmpty(ws.plan),\n\t\townerId: stringOrEmpty(ws.ownerId),\n\t\tisPersonal: booleanOrFalse(ws.isPersonal),\n\t\tisArchived: ws.archivedAt !== null,\n\t\tarchivedAt: stringOrEmpty(ws.archivedAt),\n\t\tcreatedAt: stringOrEmpty(ws.createdAt),\n\t\tupdatedAt: stringOrEmpty(ws.updatedAt),\n\t};\n}\n\nexport function toRoleDTO(role: IRole): IRoleDTO {\n\treturn {\n\t\tid: stringOrEmpty(role.id),\n\t\tname: stringOrEmpty(role.name),\n\t\tisSystem: booleanOrFalse(role.isSystem),\n\t\tactive: role.active !== false,\n\t\tdescription: stringOrEmpty(role.description),\n\t\tworkspaceId: stringOrEmpty(role.workspaceId),\n\t};\n}\n\nexport function toMemberDTO(m: IMember): IMemberDTO {\n\treturn {\n\t\tuserId: stringOrEmpty(m.userId),\n\t\tworkspaceId: stringOrEmpty(m.workspaceId),\n\t\troleId: stringOrEmpty(m.roleId),\n\t\troleName: stringOrEmpty(m.roleName),\n\t\tconfirmed: booleanOrFalse(m.confirmed),\n\t\tcreatedAt: stringOrEmpty(m.createdAt),\n\t};\n}\n\nexport function toInvitationDTO(inv: IInvitation): IInvitationDTO {\n\treturn {\n\t\tid: stringOrEmpty(inv.id),\n\t\tworkspaceId: stringOrEmpty(inv.workspaceId),\n\t\temail: stringOrEmpty(inv.email),\n\t\troleId: stringOrEmpty(inv.roleId),\n\t\ttoken: stringOrEmpty(inv.token),\n\t\tstatus: stringOrEmpty(inv.status),\n\t\texpiresAt: stringOrEmpty(inv.expiresAt),\n\t\tcreatedAt: stringOrEmpty(inv.createdAt),\n\t};\n}\n\nexport function toSettingsDTO(s: IWorkspaceSettings): IWorkspaceSettingsDTO {\n\treturn {\n\t\tlocale: stringOrEmpty(s.locale),\n\t\ttimezone: stringOrEmpty(s.timezone),\n\t\tcurrency: stringOrEmpty(s.currency),\n\t\tdateFormat: stringOrEmpty(s.dateFormat),\n\t\ttimeFormat: stringOrEmpty(s.timeFormat),\n\t};\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { MemberModel } from '../models/member.model';\nimport { toMemberDTO, toRoleDTO } from '../dtos/workspace';\n\nexport function memberController(store: IStoreAdapter) {\n\tconst members = new MemberModel(store);\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst list = await members.list(ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'MEMBERS_FETCHED', 'Members retrieved successfully.', {\n\t\t\t\tmembers: list.map(toMemberDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync remove(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\tif (ctx.workspace.isPersonal) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.FORBIDDEN,\n\t\t\t\t\t'FORBIDDEN',\n\t\t\t\t\t'Personal workspaces do not support member management',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst userId = params?.['userId'];\n\n\t\t\tif (!userId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'userId is required');\n\t\t\tif (userId === ctx.user?.id) {\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVALID_OPERATION', 'Cannot remove yourself');\n\t\t\t}\n\n\t\t\tawait members.remove(userId, ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'MEMBER_REMOVED', 'Member removed successfully.');\n\t\t},\n\n\t\tasync getUserRoles(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst userId = params?.['userId'];\n\t\t\tif (!userId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'userId is required');\n\n\t\t\tconst roles = await members.getUserRoles(userId, ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'ROLES_FETCHED', 'Member roles retrieved successfully.', {\n\t\t\t\troles: roles.map(toRoleDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync addRole(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst userId = params?.['userId'];\n\t\t\tconst roleId = (body?.['roleId'] ?? params?.['roleId']) as string | undefined;\n\n\t\t\tif (!userId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'userId is required');\n\t\t\tif (!roleId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'roleId is required');\n\n\t\t\tawait members.addRole(userId, ctx.workspace.id, roleId);\n\t\t\treturn setApiResponse(HTTP.OK, 'ROLE_ASSIGNED', 'Role assigned successfully.');\n\t\t},\n\n\t\tasync removeRole(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst userId = params?.['userId'];\n\t\t\tconst roleId = params?.['roleId'];\n\n\t\t\tif (!userId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'userId is required');\n\t\t\tif (!roleId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'roleId is required');\n\n\t\t\ttry {\n\t\t\t\tawait members.removeRole(userId, ctx.workspace.id, roleId);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'ROLE_REMOVED', 'Role removed successfully.');\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : 'Failed';\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'OPERATION_FAILED', message);\n\t\t\t}\n\t\t},\n\t};\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { RoleModel } from '../models/role.model';\nimport { toRoleDTO } from '../dtos/workspace';\n\nexport function roleController(store: IStoreAdapter) {\n\tconst roles = new RoleModel(store);\n\n\treturn {\n\t\tasync create(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst name = body?.['name'];\n\t\t\tconst description = body?.['description'];\n\n\t\t\tif (typeof name !== 'string' || name.trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'name is required');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst opts: Parameters<typeof roles.create>[0] = {\n\t\t\t\t\tname: name.trim(),\n\t\t\t\t\tworkspaceId: ctx.workspace.id,\n\t\t\t\t};\n\n\t\t\t\tif (typeof description === 'string') {\n\t\t\t\t\topts.description = description;\n\t\t\t\t}\n\n\t\t\t\tconst role = await roles.create(opts);\n\t\t\t\treturn setApiResponse(HTTP.CREATED, 'ROLE_CREATED', 'Role created successfully.', {\n\t\t\t\t\trole: toRoleDTO(role),\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : 'Failed to create role';\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'OPERATION_FAILED', message);\n\t\t\t}\n\t\t},\n\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\t}\n\n\t\t\tconst list = await roles.list(ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'ROLES_FETCHED', 'Roles retrieved successfully.', {\n\t\t\t\troles: list.map(toRoleDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync get(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst roleId = params?.['roleId'];\n\t\t\tif (!roleId) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'roleId is required');\n\t\t\t}\n\n\t\t\tconst role = await roles.findById(roleId);\n\t\t\tif (!role) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Role not found');\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.OK, 'ROLE_FETCHED', 'Role retrieved successfully.', {\n\t\t\t\trole: toRoleDTO(role),\n\t\t\t});\n\t\t},\n\n\t\tasync update(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst roleId = params?.['roleId'];\n\t\t\tif (!roleId) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'roleId is required');\n\t\t\t}\n\n\t\t\tconst opts: { name?: string; description?: string | null; active?: boolean } = {};\n\n\t\t\tif (typeof body?.['name'] === 'string') {\n\t\t\t\topts.name = body['name'];\n\t\t\t}\n\n\t\t\tif (typeof body?.['description'] === 'string') {\n\t\t\t\topts.description = body['description'];\n\t\t\t}\n\n\t\t\tif (body?.['description'] === null) {\n\t\t\t\topts.description = null;\n\t\t\t}\n\n\t\t\tif (typeof body?.['active'] === 'boolean') {\n\t\t\t\topts.active = body['active'];\n\t\t\t}\n\n\t\t\tconst role = await roles.update(roleId, opts);\n\t\t\tif (!role) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Role not found or is a system role');\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.OK, 'ROLE_UPDATED', 'Role updated successfully.', {\n\t\t\t\trole: toRoleDTO(role),\n\t\t\t});\n\t\t},\n\n\t\tasync remove(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst roleId = params?.['roleId'];\n\t\t\tif (!roleId) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'roleId is required');\n\t\t\t}\n\n\t\t\tawait roles.delete(roleId, ctx.workspace.id);\n\n\t\t\treturn setApiResponse(HTTP.OK, 'ROLE_DELETED', 'Role deleted successfully.');\n\t\t},\n\n\t\tasync setPermissions(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst roleId = params?.['roleId'];\n\t\t\tif (!roleId) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'roleId is required');\n\t\t\t}\n\n\t\t\tconst perms = body?.['permissions'];\n\t\t\tif (!Array.isArray(perms)) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t'permissions array is required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst normalized = perms\n\t\t\t\t.map((p: unknown) => {\n\t\t\t\t\tconst perm = p as Record<string, unknown>;\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpermissionKey: String(perm['permissionKey'] ?? ''),\n\t\t\t\t\t\tcanCreate: Boolean(perm['canCreate']),\n\t\t\t\t\t\tcanRead: Boolean(perm['canRead']),\n\t\t\t\t\t\tcanUpdate: Boolean(perm['canUpdate']),\n\t\t\t\t\t\tcanDelete: Boolean(perm['canDelete']),\n\t\t\t\t\t};\n\t\t\t\t})\n\t\t\t\t.filter((p) => p.permissionKey.length > 0);\n\n\t\t\tawait roles.setPermissions(roleId, ctx.workspace.id, normalized);\n\n\t\t\treturn setApiResponse(HTTP.OK, 'PERMISSIONS_SET', 'Role permissions updated successfully.');\n\t\t},\n\t};\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { ICourierMessage } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport type { EventBus } from '@fonderie/events';\nimport { NOTIFICATION_EVENT } from '@fonderie/events';\n\nimport { getPlanLimit } from '@fonderie/billing';\nimport { MESSAGE_KEYS } from '../config';\nimport { InvitationModel } from '../models/invitation.model';\nimport { toInvitationDTO } from '../dtos/workspace';\n\nexport function invitationController(store: IStoreAdapter, ttl: string, bus?: EventBus) {\n\tconst invitations = new InvitationModel(store);\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst list = await invitations.list(ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'INVITATIONS_FETCHED', 'Invitations retrieved successfully.', {\n\t\t\t\tinvitations: list.map(toInvitationDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync invite(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\tif (ctx.workspace.isPersonal) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.FORBIDDEN,\n\t\t\t\t\t'FORBIDDEN',\n\t\t\t\t\t'Personal workspaces do not support invitations',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as unknown;\n\t\t\tconst entries = Array.isArray(body) ? body as Record<string, unknown>[] : [body as Record<string, unknown>];\n\n\t\t\tif (!entries.length) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'at least one invite is required');\n\t\t\t}\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (typeof entry?.['email'] !== 'string') {\n\t\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'email is required for every invite');\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Seat limit — enforced automatically when billing module is registered.\n\t\t\t// getPlanLimit reads from ctx.meta['billing'] (fail-open when billing absent).\n\t\t\tconst seatLimit = getPlanLimit(ctx, 'seats');\n\t\t\tif (seatLimit !== null) {\n\t\t\t\tconst [countRow] = await store.query<{ count: string }>(\n\t\t\t\t\t`SELECT COUNT(*) AS count FROM fonderie_role_user_workspaces\n\t\t\t\t\t WHERE workspace_id = $1 AND removed = false AND suspended = false`,\n\t\t\t\t\t[ctx.workspace.id],\n\t\t\t\t);\n\t\t\t\tconst total = parseInt(countRow!.count, 10);\n\t\t\t\tconst occupied = ctx.workspace.isPersonal ? total : Math.max(0, total - 1);\n\t\t\t\tif (occupied + entries.length > seatLimit) {\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.PAYMENT_REQUIRED,\n\t\t\t\t\t\t'SEAT_LIMIT_REACHED',\n\t\t\t\t\t\t`Your plan allows ${seatLimit} seat${seatLimit === 1 ? '' : 's'}. Upgrade to invite more members.`,\n\t\t\t\t\t\t{ limit: seatLimit },\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Resolve default role once for entries that omit roleId.\n\t\t\t// Invitations must never default to a privileged role: fall back to\n\t\t\t// the seeded system GUEST role (least privilege). Granting anything\n\t\t\t// more requires an explicit roleId from GET /workspaces/roles.\n\t\t\tlet defaultRoleId: string | undefined;\n\t\t\tconst needsDefault = entries.some((e) => !e['roleId']);\n\t\t\tif (needsDefault) {\n\t\t\t\tconst [row] = await store.query<{ id: string }>(\n\t\t\t\t\t`SELECT id FROM fonderie_roles\n\t\t\t\t\t WHERE name = 'GUEST' AND workspace_id IS NULL AND is_system = true\n\t\t\t\t\t LIMIT 1`,\n\t\t\t\t);\n\t\t\t\tdefaultRoleId = row?.id;\n\t\t\t\tif (!defaultRoleId) {\n\t\t\t\t\treturn setApiResponse(HTTP.SERVER_ERROR, 'SERVER_ERROR', 'Default role not found');\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst results = await Promise.all(\n\t\t\t\tentries.map(async (entry) => {\n\t\t\t\t\tconst email = entry['email'] as string;\n\t\t\t\t\tconst resolvedRoleId = (entry['roleId'] as string | undefined) ?? defaultRoleId!;\n\n\t\t\t\t\tconst invitation = await invitations.create({\n\t\t\t\t\t\tworkspaceId: ctx.workspace!.id,\n\t\t\t\t\t\temail,\n\t\t\t\t\t\troleId: resolvedRoleId,\n\t\t\t\t\t\tttl,\n\t\t\t\t\t});\n\n\t\t\t\t\tbus\n\t\t\t\t\t\t?.emit(NOTIFICATION_EVENT, {\n\t\t\t\t\t\t\ttype: MESSAGE_KEYS.workspaceInvitation,\n\t\t\t\t\t\t\trecipient: { email, phone: null, deviceToken: null },\n\t\t\t\t\t\t\tdata: { token: invitation.token, pin: invitation.pin },\n\t\t\t\t\t\t} satisfies ICourierMessage)\n\t\t\t\t\t\t.catch(() => {});\n\n\t\t\t\t\treturn { invitationId: invitation.id, email };\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn setApiResponse(HTTP.CREATED, 'INVITATIONS_SENT', 'Invitations sent successfully.', {\n\t\t\t\tinvitations: results,\n\t\t\t});\n\t\t},\n\n\t\tasync cancel(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst invitationId = params?.['inviteId'];\n\n\t\t\tif (!invitationId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'inviteId is required');\n\n\t\t\tawait invitations.cancel(invitationId, ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'INVITATION_CANCELLED', 'Invitation cancelled successfully.');\n\t\t},\n\n\t\tasync accept(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst pin = body?.['pin'];\n\n\t\t\tif (typeof pin !== 'string') {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'pin is required');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { workspaceId } = await invitations.acceptByPin({ pin, userId: ctx.user!.id });\n\t\t\t\treturn setApiResponse(HTTP.OK, 'INVITATION_ACCEPTED', 'Invitation accepted successfully.', {\n\t\t\t\t\tworkspaceId,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : 'Invalid invitation';\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVITATION_FAILED', message);\n\t\t\t}\n\t\t},\n\t};\n}\n","import { randomBytes } from 'node:crypto';\n\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IInvitation } from '../types';\n\nfunction generateToken(): string {\n\treturn randomBytes(32).toString('hex');\n}\n\nfunction generatePin(): string {\n\treturn Math.floor(100000 + Math.random() * 900000).toString();\n}\n\nfunction parseTtl(ttl: string): number {\n\tconst units: Record<string, number> = {\n\t\ts: 1000,\n\t\tm: 60_000,\n\t\th: 3_600_000,\n\t\td: 86_400_000,\n\t};\n\tconst match = ttl.match(/^(\\d+)([smhd])$/);\n\tif (!match) return 7 * 86_400_000;\n\tconst [, n, unit] = match;\n\treturn parseInt(n!, 10) * (units[unit!] ?? 0);\n}\n\nconst SELECT_INV = `\n\tid,\n\tworkspace_id AS \"workspaceId\",\n\temail,\n\trole_id AS \"roleId\",\n\ttoken,\n\tpin,\n\tstatus,\n\texpires_at AS \"expiresAt\",\n\tcreated_at AS \"createdAt\"\n`;\n\nexport async function createInvitation(\n\topts: { workspaceId: string; email: string; roleId: string; ttl?: string },\n\tstore: IStoreAdapter,\n): Promise<IInvitation> {\n\tconst token = generateToken();\n\tconst pin = generatePin();\n\tconst expiresAt = new Date(Date.now() + parseTtl(opts.ttl ?? '7d'));\n\n\tconst [invitation] = await store.query<IInvitation>(\n\t\t`INSERT INTO fonderie_workspace_invitations\n\t\t (workspace_id, email, role_id, token, pin, expires_at)\n\t\t VALUES ($1, $2, $3, $4, $5, $6)\n\t\t ON CONFLICT DO NOTHING\n\t\t RETURNING ${SELECT_INV}`,\n\t\t[opts.workspaceId, opts.email, opts.roleId, token, pin, expiresAt],\n\t);\n\n\tif (!invitation) {\n\t\t// Already pending — update it\n\t\tconst [updated] = await store.query<IInvitation>(\n\t\t\t`UPDATE fonderie_workspace_invitations\n\t\t\t SET token = $4, pin = $5, expires_at = $6, role_id = $3, status = 'PENDING'\n\t\t\t WHERE workspace_id = $1 AND email = $2 AND status = 'PENDING'\n\t\t\t RETURNING ${SELECT_INV}`,\n\t\t\t[opts.workspaceId, opts.email, opts.roleId, token, pin, expiresAt],\n\t\t);\n\t\tif (!updated) throw new Error('Failed to create invitation');\n\t\treturn updated;\n\t}\n\n\treturn invitation;\n}\n\nexport async function listInvitations(\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<IInvitation[]> {\n\treturn store.query<IInvitation>(\n\t\t`SELECT ${SELECT_INV}\n\t\t FROM fonderie_workspace_invitations\n\t\t WHERE workspace_id = $1 AND status = 'PENDING'\n\t\t ORDER BY created_at DESC`,\n\t\t[workspaceId],\n\t);\n}\n\nexport async function cancelInvitation(\n\tinvitationId: string,\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`UPDATE fonderie_workspace_invitations\n\t\t SET status = 'CANCELLED'\n\t\t WHERE id = $1 AND workspace_id = $2 AND status = 'PENDING'`,\n\t\t[invitationId, workspaceId],\n\t);\n}\n\nexport async function acceptInvitationByPin(\n\topts: { pin: string; userId: string },\n\tstore: IStoreAdapter,\n): Promise<{ workspaceId: string; roleId: string }> {\n\tconst [inv] = await store.query<{\n\t\tid: string;\n\t\tworkspaceId: string;\n\t\troleId: string;\n\t\texpiresAt: string;\n\t}>(\n\t\t`SELECT id, workspace_id AS \"workspaceId\", role_id AS \"roleId\", expires_at AS \"expiresAt\"\n\t\t FROM fonderie_workspace_invitations\n\t\t WHERE pin = $1 AND status = 'PENDING'`,\n\t\t[opts.pin],\n\t);\n\n\tif (!inv) throw new Error('Invalid PIN');\n\tif (new Date() > new Date(inv.expiresAt)) throw new Error('Invitation expired');\n\n\tawait store.transaction(async (tx) => {\n\t\tawait Promise.all([\n\t\t\ttx.query(\n\t\t\t\t`INSERT INTO fonderie_role_user_workspaces (user_id, workspace_id, role_id, confirmed)\n\t\t\t\t VALUES ($1, $2, $3, true)\n\t\t\t\t ON CONFLICT (user_id, workspace_id, role_id) DO UPDATE\n\t\t\t\t SET confirmed = true, removed = false`,\n\t\t\t\t[opts.userId, inv.workspaceId, inv.roleId],\n\t\t\t),\n\t\t\ttx.query(`UPDATE fonderie_workspace_invitations SET status = 'ACCEPTED' WHERE id = $1`, [\n\t\t\t\tinv.id,\n\t\t\t]),\n\t\t]);\n\t});\n\n\treturn { workspaceId: inv.workspaceId, roleId: inv.roleId };\n}\n\nexport async function acceptInvitationByToken(\n\ttoken: string,\n\tuserId: string,\n\tstore: IStoreAdapter,\n): Promise<{ workspaceId: string; roleId: string }> {\n\tconst [inv] = await store.query<{\n\t\tid: string;\n\t\tworkspaceId: string;\n\t\troleId: string;\n\t\texpiresAt: string;\n\t}>(\n\t\t`SELECT id, workspace_id AS \"workspaceId\", role_id AS \"roleId\", expires_at AS \"expiresAt\"\n\t\t FROM fonderie_workspace_invitations\n\t\t WHERE token = $1 AND status = 'PENDING'`,\n\t\t[token],\n\t);\n\n\tif (!inv) throw new Error('Invalid token');\n\tif (new Date() > new Date(inv.expiresAt)) throw new Error('Invitation expired');\n\n\tawait store.transaction(async (tx) => {\n\t\tawait Promise.all([\n\t\t\ttx.query(\n\t\t\t\t`INSERT INTO fonderie_role_user_workspaces (user_id, workspace_id, role_id, confirmed)\n\t\t\t\t VALUES ($1, $2, $3, true)\n\t\t\t\t ON CONFLICT (user_id, workspace_id, role_id) DO UPDATE\n\t\t\t\t SET confirmed = true, removed = false`,\n\t\t\t\t[userId, inv.workspaceId, inv.roleId],\n\t\t\t),\n\t\t\ttx.query(`UPDATE fonderie_workspace_invitations SET status = 'ACCEPTED' WHERE id = $1`, [\n\t\t\t\tinv.id,\n\t\t\t]),\n\t\t]);\n\t});\n\n\treturn { workspaceId: inv.workspaceId, roleId: inv.roleId };\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IInvitation } from '../types';\nimport {\n\tcreateInvitation,\n\tlistInvitations,\n\tcancelInvitation,\n\tacceptInvitationByPin,\n} from '../services/invitations';\n\nexport class InvitationModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tcreate(opts: Parameters<typeof createInvitation>[0]): Promise<IInvitation> {\n\t\treturn createInvitation(opts, this.store);\n\t}\n\n\tlist(workspaceId: string): Promise<IInvitation[]> {\n\t\treturn listInvitations(workspaceId, this.store);\n\t}\n\n\tcancel(invitationId: string, workspaceId: string): Promise<void> {\n\t\treturn cancelInvitation(invitationId, workspaceId, this.store);\n\t}\n\n\tacceptByPin(\n\t\topts: Parameters<typeof acceptInvitationByPin>[0],\n\t): Promise<{ workspaceId: string; roleId: string }> {\n\t\treturn acceptInvitationByPin(opts, this.store);\n\t}\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport type { EventBus } from '@fonderie/events';\n\nimport type { IWorkspacesConfig } from './config';\nimport { EVENT_KEYS } from './config';\nimport { buildWorkspaceRoutes } from './routes';\nimport { WorkspaceModel } from './models/workspace.model';\nimport { RoleModel } from './models/role.model';\nimport { MemberModel } from './models/member.model';\n\n// Mirror of @fonderie/auth EVENT_KEYS.userRegistered — avoids a runtime\n// dependency on the auth package while remaining explicit about the contract.\nconst AUTH_USER_REGISTERED = 'fonderie.user.registered' as const;\n\ninterface UserRegisteredPayload {\n\tuserId: string;\n}\n\nexport class WorkspacesModule implements IFonderieModule {\n\treadonly name = '@fonderie/workspaces';\n\treadonly deps = ['@fonderie/auth', '@fonderie/billing'];\n\n\tconstructor(\n\t\tprivate store: IStoreAdapter,\n\t\tprivate config: IWorkspacesConfig = {},\n\t\tprivate bus?: EventBus,\n\t) {}\n\n\tinstall(app: IFonderieApp): void {\n\t\tif (this.bus && this.config.personalWorkspace !== false) {\n\t\t\tthis.bus.on<UserRegisteredPayload>(\n\t\t\t\tAUTH_USER_REGISTERED,\n\t\t\t\t(payload) => this.provisionPersonalWorkspace(payload),\n\t\t\t\t'workspaces',\n\t\t\t);\n\t\t}\n\n\t\tconst routes = buildWorkspaceRoutes(this.store, this.config, this.bus);\n\t\tfor (const [method, path, ...handlers] of routes) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n\n\tprivate async provisionPersonalWorkspace(payload: UserRegisteredPayload): Promise<void> {\n\t\tconst name = 'My Workspace';\n\t\tconst slug = `${payload.userId}-personal`;\n\n\t\tlet workspaceId: string | undefined;\n\n\t\tawait this.store.transaction(async (tx) => {\n\t\t\tconst wsModel = new WorkspaceModel(tx);\n\t\t\tconst roleModel = new RoleModel(tx);\n\t\t\tconst memModel = new MemberModel(tx);\n\n\t\t\tconst ws = await wsModel.createPersonal({ name, slug, ownerId: payload.userId });\n\t\t\tif (!ws) return; // already exists — idempotent replay\n\n\t\t\tworkspaceId = ws.id;\n\n\t\t\tconst adminRole = await roleModel.findSystem('ADMIN');\n\t\t\tif (!adminRole) throw new Error('System ADMIN role not found');\n\n\t\t\tawait memModel.add({\n\t\t\t\tuserId: payload.userId,\n\t\t\t\tworkspaceId: ws.id,\n\t\t\t\troleId: adminRole.id,\n\t\t\t\tconfirmed: true,\n\t\t\t});\n\t\t});\n\n\t\tif (workspaceId) {\n\t\t\tthis.bus\n\t\t\t\t?.emit(EVENT_KEYS.personalWorkspaceCreated, {\n\t\t\t\t\tworkspaceId,\n\t\t\t\t\tuserId: payload.userId,\n\t\t\t\t})\n\t\t\t\t.catch(() => {});\n\t\t}\n\t}\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { Middleware } from '@fonderie/core';\n\n// Ensures ctx.workspace is set — use after withWorkspace\n\nexport const requireWorkspace: Middleware = async (ctx, next) => {\n\tif (!ctx.workspace) {\n\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'WORKSPACE_REQUIRED', 'Workspace context required');\n\t}\n\treturn next();\n};\n"],"mappings":"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","names":["setApiResponse","HTTP","name","name","setApiResponse","HTTP","name","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","name","setApiResponse","HTTP","setApiResponse","HTTP","email","name","setApiResponse","HTTP"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/routes.ts","../src/schemas.ts","../src/middlewares/workspace-context.ts","../src/services/members.ts","../src/services/workspaces.ts","../src/controllers/workspace.controller.ts","../src/models/workspace.model.ts","../src/models/member.model.ts","../src/services/roles.ts","../src/models/role.model.ts","../src/dtos/workspace.ts","../src/controllers/member.controller.ts","../src/controllers/role.controller.ts","../src/controllers/invitation.controller.ts","../src/services/invitations.ts","../src/models/invitation.model.ts","../src/module.ts","../src/middlewares/require-workspace.ts"],"sourcesContent":["export const MESSAGE_KEYS = {\n\tworkspaceInvitation: 'workspace-invitation',\n} as const;\n\nexport type WorkspacesMessageKey = (typeof MESSAGE_KEYS)[keyof typeof MESSAGE_KEYS];\n\nexport const EVENT_KEYS = {\n\tpersonalWorkspaceCreated: 'fonderie.workspace.personal.created',\n} as const;\n\nexport type WorkspacesEventKey = (typeof EVENT_KEYS)[keyof typeof EVENT_KEYS];\n\nexport interface IWorkspacesConfig {\n\t// How long invitations are valid. Default: '7d'\n\tinvitationTtl?: string;\n\n\t// Auto-create a personal workspace when user.registered fires.\n\t// Requires an EventBus to be passed to WorkspacesModule. Default: true\n\tpersonalWorkspace?: boolean;\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { Middleware } from '@fonderie/core';\nimport type { EventBus } from '@fonderie/events';\nimport { requireAuth, validate } from '@fonderie/core/middlewares';\n\nimport {\n\tcreateRoleSchema,\n\tupdateRoleSchema,\n\taddMemberRoleSchema,\n\tupdateSettingsSchema,\n\tcreateWorkspaceSchema,\n\tupdateWorkspaceSchema,\n\tacceptInvitationSchema,\n\tcreateInvitationsSchema,\n\tsetRolePermissionsSchema,\n} from './schemas';\n\nimport type { IWorkspacesConfig } from './config';\nimport { withWorkspace } from './middlewares/workspace-context';\n\nimport { workspaceController } from './controllers/workspace.controller';\nimport { memberController } from './controllers/member.controller';\nimport { roleController } from './controllers/role.controller';\nimport { invitationController } from './controllers/invitation.controller';\n\ntype RouteDefinition = [string, string, ...Middleware[]];\n\nexport function buildWorkspaceRoutes(\n\tstore: IStoreAdapter,\n\tconfig: IWorkspacesConfig,\n\tbus?: EventBus,\n): RouteDefinition[] {\n\tconst ttl = config.invitationTtl ?? '7d';\n\tconst wsCtx = withWorkspace(store);\n\n\tconst workspace = workspaceController(store, config);\n\tconst member = memberController(store);\n\tconst role = roleController(store);\n\tconst invitation = invitationController(store, ttl, bus);\n\n\treturn [\n\t\t// ── Workspace creation + listing (no workspace context required)\n\t\t['POST', '/workspaces', requireAuth, validate(createWorkspaceSchema), workspace.create],\n\t\t['GET', '/workspaces', requireAuth, workspace.list],\n\n\t\t// ── Members (workspace resolved from X-Workspace-ID header)\n\t\t['GET', '/workspaces/members', requireAuth, wsCtx, member.list],\n\t\t['DELETE', '/workspaces/members/:userId', requireAuth, wsCtx, member.remove],\n\t\t['GET', '/workspaces/members/:userId/roles', requireAuth, wsCtx, member.getUserRoles],\n\t\t['POST', '/workspaces/members/:userId/roles', requireAuth, wsCtx, validate(addMemberRoleSchema), member.addRole],\n\t\t['DELETE', '/workspaces/members/:userId/roles/:roleId', requireAuth, wsCtx, member.removeRole],\n\n\t\t// ── Invitations\n\t\t['GET', '/workspaces/invitations', requireAuth, wsCtx, invitation.list],\n\t\t['POST', '/workspaces/invitations', requireAuth, wsCtx, validate(createInvitationsSchema), invitation.invite],\n\t\t['DELETE', '/workspaces/invitations/:inviteId', requireAuth, wsCtx, invitation.cancel],\n\t\t['POST', '/workspaces/invitations/accept', requireAuth, validate(acceptInvitationSchema), invitation.accept],\n\n\t\t// ── Roles\n\t\t['POST', '/workspaces/roles', requireAuth, wsCtx, validate(createRoleSchema), role.create],\n\t\t['GET', '/workspaces/roles', requireAuth, wsCtx, role.list],\n\t\t['GET', '/workspaces/roles/:roleId', requireAuth, wsCtx, role.get],\n\t\t['PUT', '/workspaces/roles/:roleId', requireAuth, wsCtx, validate(updateRoleSchema), role.update],\n\t\t['DELETE', '/workspaces/roles/:roleId', requireAuth, wsCtx, role.remove],\n\t\t['POST', '/workspaces/roles/:roleId/permissions', requireAuth, wsCtx, validate(setRolePermissionsSchema), role.setPermissions],\n\n\t\t// ── Workspace lifecycle\n\t\t['POST', '/workspaces/archive', requireAuth, wsCtx, workspace.archive],\n\t\t['POST', '/workspaces/restore', requireAuth, wsCtx, workspace.restore],\n\t\t['GET', '/workspaces/settings', requireAuth, wsCtx, workspace.getSettings],\n\t\t['PUT', '/workspaces/settings', requireAuth, wsCtx, validate(updateSettingsSchema), workspace.updateSettings],\n\n\t\t// ── Path-based lookup by ID (admin / cross-workspace use)\n\t\t['GET', '/workspaces/:id', requireAuth, wsCtx, workspace.get],\n\n\t\t// ── Update current workspace — ID resolved from X-Workspace-ID header\n\t\t// (or personal workspace fallback when header is absent)\n\t\t['PUT', '/workspaces', requireAuth, wsCtx, validate(updateWorkspaceSchema), workspace.update],\n\t];\n}\n","import { z } from 'zod';\n\n// Request schemas — the validation contract for every body-taking workspaces\n// route. Wired into routes.ts via @fonderie/core's validate(); exported so\n// docs generators and typed clients read the source of truth the runtime\n// enforces. Same pattern as @fonderie/auth/src/schemas.ts.\n\nconst name = z.string().trim().min(1, 'name is required').max(200);\nconst email = z.string().trim().pipe(z.email());\n\nexport const createWorkspaceSchema = z.object({\n\tname,\n\tdescription: z.string().max(2000).optional(),\n\t// PERSONAL workspaces are created automatically by the module; the\n\t// controller enforces the rule — schema just types the field.\n\ttype: z.string().max(40).optional(),\n});\n\nconst addressSchema = z\n\t.object({\n\t\tline1: z.string().max(200).optional(),\n\t\tline2: z.string().max(200).optional(),\n\t\tcity: z.string().max(100).optional(),\n\t\tregion: z.string().max(100).optional(),\n\t\tpostalCode: z.string().max(20).optional(),\n\t\tcountry: z.string().max(60).optional(),\n\t})\n\t.passthrough();\n\nexport const updateWorkspaceSchema = z\n\t.object({\n\t\tname: z.string().trim().min(1).max(200).optional(),\n\t\tdescription: z.string().max(2000).nullable().optional(),\n\t\tmotto: z.string().max(300).nullable().optional(),\n\t\tphone: z.string().max(32).nullable().optional(),\n\t\tbusinessType: z.string().max(100).nullable().optional(),\n\t\taddress: addressSchema.nullable().optional(),\n\t})\n\t.refine((o) => Object.values(o).some((v) => v !== undefined), 'Provide at least one field');\n\nexport const updateSettingsSchema = z\n\t.object({\n\t\tlocale: z.string().max(35).optional(),\n\t\ttimezone: z.string().max(64).optional(),\n\t\tcurrency: z.string().max(8).optional(),\n\t\tdateFormat: z.string().max(32).optional(),\n\t\ttimeFormat: z.string().max(32).optional(),\n\t})\n\t.refine((o) => Object.values(o).some((v) => v !== undefined), 'No settings provided');\n\nexport const addMemberRoleSchema = z.object({ roleId: z.string().min(1, 'roleId is required') });\n\nconst inviteEntry = z.object({\n\temail,\n\troleId: z.string().min(1).optional(),\n});\n\n// POST /workspaces/invitations accepts a single invite or a batch.\nexport const createInvitationsSchema = z.union([\n\tinviteEntry,\n\tz.array(inviteEntry).min(1, 'at least one invite is required'),\n]);\n\nexport const acceptInvitationSchema = z.object({ pin: z.string().trim().min(1, 'pin is required') });\n\nexport const createRoleSchema = z.object({\n\tname,\n\tdescription: z.string().max(1000).optional(),\n});\n\nexport const updateRoleSchema = z\n\t.object({\n\t\tname: z.string().trim().min(1).max(200).optional(),\n\t\tdescription: z.string().max(1000).nullable().optional(),\n\t\tactive: z.boolean().optional(),\n\t})\n\t.refine((o) => Object.values(o).some((v) => v !== undefined), 'Provide at least one field');\n\nexport const setRolePermissionsSchema = z.object({\n\tpermissions: z.array(\n\t\tz.object({\n\t\t\tpermissionKey: z.string().min(1),\n\t\t\tcanCreate: z.boolean().optional(),\n\t\t\tcanRead: z.boolean().optional(),\n\t\t\tcanUpdate: z.boolean().optional(),\n\t\t\tcanDelete: z.boolean().optional(),\n\t\t}),\n\t),\n});\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { Middleware } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { getMember } from '../services/members';\nimport { findWorkspaceById, findPersonalWorkspace } from '../services/workspaces';\n\n// Resolves ctx.workspace from:\n// 1. Route param :workspaceId or :id (path-based admin routes)\n// 2. X-Workspace-ID request header (standard resource routes)\n// Validates the current user is an active member.\n// Must run after withSession.\n\nfunction makeHandler(store: IStoreAdapter): Middleware {\n\treturn async (ctx, next) => {\n\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\tconst workspaceId =\n\t\t\tparams?.['workspaceId'] ??\n\t\t\tparams?.['id'] ??\n\t\t\tctx.request.headers.get('x-workspace-id') ??\n\t\t\tundefined;\n\n\t\tif (!workspaceId) {\n\t\t\t// DMZ fallback — if the caller didn't specify a workspace, use their personal one.\n\t\t\t// No membership check needed: the user is always the sole owner.\n\t\t\tif (ctx.user) {\n\t\t\t\tconst personal = await findPersonalWorkspace(ctx.user.id, store);\n\t\t\t\tif (personal) Object.assign(ctx, { workspace: personal });\n\t\t\t}\n\t\t\treturn next();\n\t\t}\n\n\t\tconst workspace = await findWorkspaceById(workspaceId, store);\n\t\tif (!workspace) {\n\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t}\n\n\t\tif (ctx.user) {\n\t\t\tconst member = await getMember(ctx.user.id, workspaceId, store);\n\t\t\tif (!member) {\n\t\t\t\treturn setApiResponse(HTTP.FORBIDDEN, 'FORBIDDEN', 'Not a member of this workspace');\n\t\t\t}\n\t\t}\n\n\t\tObject.assign(ctx, { workspace });\n\t\treturn next();\n\t};\n}\n\nexport function withWorkspace(store: IStoreAdapter): Middleware;\nexport function withWorkspace(\n\tstore: IStoreAdapter,\n\tctx: IFonderieContext,\n\tnext: () => Promise<Response>,\n): Promise<Response>;\nexport function withWorkspace(\n\tstore: IStoreAdapter,\n\tctx?: IFonderieContext,\n\tnext?: () => Promise<Response>,\n): Middleware | Promise<Response> {\n\tconst handler = makeHandler(store);\n\tif (ctx !== undefined && next !== undefined) return handler(ctx, next);\n\treturn handler;\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IMember, IRole } from '../types';\n\nconst SELECT_MEMBER = `\n\truw.user_id AS \"userId\",\n\truw.workspace_id AS \"workspaceId\",\n\truw.role_id AS \"roleId\",\n\tr.name AS \"roleName\",\n\truw.confirmed AS \"confirmed\",\n\truw.created_at AS \"createdAt\",\n\tu.first_name AS \"firstName\",\n\tu.last_name AS \"lastName\",\n\tu.email AS \"email\",\n\tu.profile_image_url AS \"profileImageUrl\"\n`;\n\nexport async function getMember(\n\tuserId: string,\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<IMember | null> {\n\tconst [row] = await store.query<IMember>(\n\t\t`SELECT ${SELECT_MEMBER}\n\t\t FROM fonderie_role_user_workspaces ruw\n\t\t LEFT JOIN fonderie_roles r ON r.id = ruw.role_id\n\t\t LEFT JOIN fonderie_users u ON u.id = ruw.user_id\n\t\t WHERE ruw.user_id = $1\n\t\t AND ruw.workspace_id = $2\n\t\t AND ruw.removed = false\n\t\t AND ruw.suspended = false\n\t\t LIMIT 1`,\n\t\t[userId, workspaceId],\n\t);\n\treturn row ?? null;\n}\n\nexport async function listMembers(workspaceId: string, store: IStoreAdapter): Promise<IMember[]> {\n\treturn store.query<IMember>(\n\t\t`SELECT ${SELECT_MEMBER}\n\t\t FROM fonderie_role_user_workspaces ruw\n\t\t LEFT JOIN fonderie_roles r ON r.id = ruw.role_id\n\t\t LEFT JOIN fonderie_users u ON u.id = ruw.user_id\n\t\t WHERE ruw.workspace_id = $1\n\t\t AND ruw.removed = false\n\t\t AND ruw.suspended = false\n\t\t ORDER BY ruw.created_at ASC`,\n\t\t[workspaceId],\n\t);\n}\n\nexport async function addMember(\n\topts: { userId: string; workspaceId: string; roleId: string; confirmed?: boolean },\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`INSERT INTO fonderie_role_user_workspaces (user_id, workspace_id, role_id, confirmed)\n\t\t VALUES ($1, $2, $3, $4)\n\t\t ON CONFLICT (user_id, workspace_id, role_id) DO UPDATE\n\t\t SET confirmed = $4, removed = false, suspended = false`,\n\t\t[opts.userId, opts.workspaceId, opts.roleId, opts.confirmed ?? true],\n\t);\n}\n\nexport async function removeMember(\n\tuserId: string,\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`UPDATE fonderie_role_user_workspaces\n\t\t SET removed = true\n\t\t WHERE user_id = $1\n\t\t AND workspace_id = $2`,\n\t\t[userId, workspaceId],\n\t);\n}\n\nexport async function getUserRoles(\n\tuserId: string,\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<IRole[]> {\n\treturn store.query<IRole>(\n\t\t`SELECT\n\t\t r.id,\n\t\t r.name,\n\t\t r.is_system AS \"isSystem\",\n\t\t r.active,\n\t\t r.description,\n\t\t r.workspace_id AS \"workspaceId\"\n\t\t FROM fonderie_role_user_workspaces ruw\n\t\t JOIN fonderie_roles r ON r.id = ruw.role_id\n\t\t WHERE ruw.user_id = $1\n\t\t AND ruw.workspace_id = $2\n\t\t AND ruw.removed = false\n\t\t AND ruw.suspended = false`,\n\t\t[userId, workspaceId],\n\t);\n}\n\nexport async function addRoleToMember(\n\tuserId: string,\n\tworkspaceId: string,\n\troleId: string,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`INSERT INTO fonderie_role_user_workspaces (user_id, workspace_id, role_id, confirmed)\n\t\t VALUES ($1, $2, $3, true)\n\t\t ON CONFLICT (user_id, workspace_id, role_id) DO UPDATE\n\t\t SET confirmed = true, removed = false, suspended = false`,\n\t\t[userId, workspaceId, roleId],\n\t);\n}\n\nexport async function removeRoleFromMember(\n\tuserId: string,\n\tworkspaceId: string,\n\troleId: string,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tconst remaining = await store.query<{ count: string }>(\n\t\t`SELECT COUNT(*) AS count\n\t\t FROM fonderie_role_user_workspaces\n\t\t WHERE user_id = $1\n\t\t AND workspace_id = $2\n\t\t AND removed = false`,\n\t\t[userId, workspaceId],\n\t);\n\tconst count = parseInt(remaining[0]?.count ?? '0', 10);\n\tif (count <= 1) throw new Error('Cannot remove last role from member');\n\n\tawait store.query(\n\t\t`DELETE FROM fonderie_role_user_workspaces\n\t\t WHERE user_id = $1\n\t\t AND workspace_id = $2\n\t\t AND role_id = $3`,\n\t\t[userId, workspaceId, roleId],\n\t);\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWorkspace, IWorkspaceAddress, IWorkspaceSettings } from '../types';\n\nconst SELECT_WS = `\n\tid,\n\tname,\n\tslug,\n\ttype,\n\tdescription,\n\tmotto,\n\tphone,\n\tbusiness_type AS \"businessType\",\n\taddress,\n\tplan,\n\towner_id AS \"ownerId\",\n\tis_personal AS \"isPersonal\",\n\tarchived_at AS \"archivedAt\",\n\tarchived_by AS \"archivedBy\",\n\tcreated_at AS \"createdAt\",\n\tupdated_at AS \"updatedAt\"\n`;\n\nconst SELECT_WS_W = `\n\tw.id,\n\tw.name,\n\tw.slug,\n\tw.type,\n\tw.description,\n\tw.motto,\n\tw.phone,\n\tw.business_type AS \"businessType\",\n\tw.address,\n\tw.plan,\n\tw.owner_id AS \"ownerId\",\n\tw.is_personal AS \"isPersonal\",\n\tw.archived_at AS \"archivedAt\",\n\tw.archived_by AS \"archivedBy\",\n\tw.created_at AS \"createdAt\",\n\tw.updated_at AS \"updatedAt\"\n`;\n\nexport async function findWorkspaceById(\n\tid: string,\n\tstore: IStoreAdapter,\n): Promise<IWorkspace | null> {\n\tconst [row] = await store.query<IWorkspace>(\n\t\t`SELECT ${SELECT_WS} FROM fonderie_workspaces WHERE id = $1`,\n\t\t[id],\n\t);\n\treturn row ?? null;\n}\n\nexport async function findWorkspacesByUserId(\n\tuserId: string,\n\tstore: IStoreAdapter,\n): Promise<IWorkspace[]> {\n\treturn store.query<IWorkspace>(\n\t\t`SELECT ${SELECT_WS_W}\n\t\t FROM fonderie_workspaces w\n\t\t JOIN fonderie_role_user_workspaces ruw ON ruw.workspace_id = w.id\n\t\t WHERE ruw.user_id = $1\n\t\t AND ruw.removed = false\n\t\t AND ruw.suspended = false\n\t\t GROUP BY w.id\n\t\t ORDER BY w.created_at ASC`,\n\t\t[userId],\n\t);\n}\n\nexport async function createWorkspace(\n\topts: {\n\t\tname: string;\n\t\tslug: string;\n\t\townerId: string;\n\t\ttype?: string;\n\t\tdescription?: string;\n\t\tplan?: string;\n\t},\n\tstore: IStoreAdapter,\n): Promise<IWorkspace> {\n\tconst [workspace] = await store.query<IWorkspace>(\n\t\t`INSERT INTO fonderie_workspaces (name, slug, owner_id, type, description, plan)\n\t\t VALUES ($1, $2, $3, $4, $5, $6)\n\t\t RETURNING ${SELECT_WS}`,\n\t\t[\n\t\t\topts.name,\n\t\t\topts.slug,\n\t\t\topts.ownerId,\n\t\t\topts.type ?? 'ORGANIZATION',\n\t\t\topts.description ?? null,\n\t\t\topts.plan ?? 'free',\n\t\t],\n\t);\n\n\tif (!workspace) throw new Error('Failed to create workspace');\n\treturn workspace;\n}\n\n// Returns the new workspace, or null if the personal workspace already exists (idempotent).\nexport async function createPersonalWorkspace(\n\topts: { name: string; slug: string; ownerId: string },\n\tstore: IStoreAdapter,\n): Promise<IWorkspace | null> {\n\tconst [workspace] = await store.query<IWorkspace>(\n\t\t`INSERT INTO fonderie_workspaces (name, slug, owner_id, type, is_personal)\n\t\t VALUES ($1, $2, $3, 'PERSONAL', true)\n\t\t ON CONFLICT (owner_id) WHERE is_personal = true DO NOTHING\n\t\t RETURNING ${SELECT_WS}`,\n\t\t[opts.name, opts.slug, opts.ownerId],\n\t);\n\treturn workspace ?? null;\n}\n\nexport async function findPersonalWorkspace(\n\tuserId: string,\n\tstore: IStoreAdapter,\n): Promise<IWorkspace | null> {\n\tconst [row] = await store.query<IWorkspace>(\n\t\t`SELECT ${SELECT_WS} FROM fonderie_workspaces\n\t\t WHERE owner_id = $1 AND is_personal = true\n\t\t LIMIT 1`,\n\t\t[userId],\n\t);\n\treturn row ?? null;\n}\n\nexport async function updateWorkspace(\n\tid: string,\n\topts: {\n\t\tname?: string;\n\t\tdescription?: string | null;\n\t\tslug?: string;\n\t\tmotto?: string | null;\n\t\tphone?: string | null;\n\t\tbusinessType?: string | null;\n\t\taddress?: IWorkspaceAddress | null;\n\t},\n\tstore: IStoreAdapter,\n): Promise<IWorkspace | null> {\n\tconst sets: string[] = ['updated_at = now()'];\n\tconst params: unknown[] = [id];\n\n\tif (opts.name !== undefined) {\n\t\tparams.push(opts.name);\n\t\tsets.push(`name = $${params.length}`);\n\t}\n\tif (opts.description !== undefined) {\n\t\tparams.push(opts.description);\n\t\tsets.push(`description = $${params.length}`);\n\t}\n\tif (opts.slug !== undefined) {\n\t\tparams.push(opts.slug);\n\t\tsets.push(`slug = $${params.length}`);\n\t}\n\tif (opts.motto !== undefined) {\n\t\tparams.push(opts.motto);\n\t\tsets.push(`motto = $${params.length}`);\n\t}\n\tif (opts.phone !== undefined) {\n\t\tparams.push(opts.phone);\n\t\tsets.push(`phone = $${params.length}`);\n\t}\n\tif (opts.businessType !== undefined) {\n\t\tparams.push(opts.businessType);\n\t\tsets.push(`business_type = $${params.length}`);\n\t}\n\tif (opts.address !== undefined) {\n\t\tparams.push(JSON.stringify(opts.address ?? {}));\n\t\tsets.push(`address = $${params.length}::jsonb`);\n\t}\n\n\tconst [row] = await store.query<IWorkspace>(\n\t\t`UPDATE fonderie_workspaces\n\t\t SET ${sets.join(', ')}\n\t\t WHERE id = $1\n\t\t RETURNING ${SELECT_WS}`,\n\t\tparams,\n\t);\n\treturn row ?? null;\n}\n\nexport async function archiveWorkspace(\n\tid: string,\n\tbyUser: string,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`UPDATE fonderie_workspaces\n\t\t SET archived_at = now(), archived_by = $2, updated_at = now()\n\t\t WHERE id = $1 AND archived_at IS NULL`,\n\t\t[id, byUser],\n\t);\n}\n\nexport async function restoreWorkspace(id: string, store: IStoreAdapter): Promise<void> {\n\tawait store.query(\n\t\t`UPDATE fonderie_workspaces\n\t\t SET archived_at = NULL, archived_by = NULL, updated_at = now()\n\t\t WHERE id = $1`,\n\t\t[id],\n\t);\n}\n\nconst SETTINGS_DEFAULTS: IWorkspaceSettings = {\n\tlocale: 'en-US',\n\ttimezone: 'UTC',\n\tcurrency: 'USD',\n\tdateFormat: 'MM/DD/YYYY',\n\ttimeFormat: 'hh:mm A',\n};\n\nexport async function getWorkspaceSettings(\n\tid: string,\n\tstore: IStoreAdapter,\n): Promise<IWorkspaceSettings> {\n\tconst [row] = await store.query<{ settings: Record<string, unknown> }>(\n\t\t`SELECT settings FROM fonderie_workspaces WHERE id = $1`,\n\t\t[id],\n\t);\n\tconst raw = row?.settings ?? {};\n\tconst s = (raw['settings'] as Record<string, unknown> | undefined) ?? raw;\n\n\treturn {\n\t\tlocale: typeof s['locale'] === 'string' ? s['locale'] : SETTINGS_DEFAULTS.locale,\n\t\ttimezone: typeof s['timezone'] === 'string' ? s['timezone'] : SETTINGS_DEFAULTS.timezone,\n\t\tcurrency: typeof s['currency'] === 'string' ? s['currency'] : SETTINGS_DEFAULTS.currency,\n\t\tdateFormat:\n\t\t\ttypeof s['dateFormat'] === 'string' ? s['dateFormat'] : SETTINGS_DEFAULTS.dateFormat,\n\t\ttimeFormat:\n\t\t\ttypeof s['timeFormat'] === 'string' ? s['timeFormat'] : SETTINGS_DEFAULTS.timeFormat,\n\t};\n}\n\nexport async function updateWorkspaceSettings(\n\tid: string,\n\tsettings: Partial<IWorkspaceSettings>,\n\tstore: IStoreAdapter,\n): Promise<IWorkspaceSettings> {\n\tawait store.query(\n\t\t`UPDATE fonderie_workspaces\n\t\t SET settings = settings || jsonb_build_object('settings', $2::jsonb),\n\t\t updated_at = now()\n\t\t WHERE id = $1`,\n\t\t[id, JSON.stringify(settings)],\n\t);\n\treturn getWorkspaceSettings(id, store);\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWorkspacesConfig } from '../config';\nimport type { IWorkspace } from '../types';\nimport { WorkspaceModel } from '../models/workspace.model';\nimport { MemberModel } from '../models/member.model';\nimport { RoleModel } from '../models/role.model';\nimport { toWorkspaceDTO, toSettingsDTO } from '../dtos/workspace';\n\nexport function workspaceController(store: IStoreAdapter, config: IWorkspacesConfig) {\n\tconst workspaces = new WorkspaceModel(store);\n\tconst members = new MemberModel(store);\n\tconst roles = new RoleModel(store);\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.user)\n\t\t\t\treturn setApiResponse(HTTP.UNAUTHORIZED, 'UNAUTHORIZED', 'Authentication required');\n\t\t\tconst list = await workspaces.findByUserId(ctx.user.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'WORKSPACES_FETCHED', 'Workspaces retrieved successfully.', {\n\t\t\t\tworkspaces: list.map(toWorkspaceDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync create(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst name = body?.['name'];\n\t\t\tconst description = body?.['description'];\n\t\t\tconst type = body?.['type'];\n\n\t\t\tif (typeof name !== 'string' || name.trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'name is required');\n\t\t\t}\n\n\t\t\tif (type === 'PERSONAL') {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t'Personal workspaces are created automatically',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst slug = name\n\t\t\t\t.toLowerCase()\n\t\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t\t.replace(/^-|-$/g, '');\n\n\t\t\tconst workspace = await store.transaction(async (tx) => {\n\t\t\t\tconst wsOpts: Parameters<typeof workspaces.create>[0] = {\n\t\t\t\t\tname: name.trim(),\n\t\t\t\t\tslug,\n\t\t\t\t\townerId: ctx.user!.id,\n\t\t\t\t\ttype: typeof type === 'string' ? type : 'ORGANIZATION',\n\t\t\t\t};\n\t\t\t\tif (typeof description === 'string') wsOpts.description = description;\n\n\t\t\t\tconst wsModel = new WorkspaceModel(tx);\n\t\t\t\tconst roleModel = new RoleModel(tx);\n\t\t\t\tconst memModel = new MemberModel(tx);\n\n\t\t\t\tconst ws = await wsModel.create(wsOpts);\n\n\t\t\t\tconst adminRole = await roleModel.findSystem('ADMIN');\n\t\t\t\tif (!adminRole) throw new Error('System ADMIN role not found');\n\t\t\t\tawait memModel.add({ userId: ctx.user!.id, workspaceId: ws.id, roleId: adminRole.id });\n\n\t\t\t\treturn ws;\n\t\t\t});\n\n\t\t\treturn setApiResponse(HTTP.CREATED, 'WORKSPACE_CREATED', 'Workspace created successfully.', {\n\t\t\t\tworkspace: toWorkspaceDTO(workspace),\n\t\t\t});\n\t\t},\n\n\t\tasync get(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\treturn setApiResponse(HTTP.OK, 'WORKSPACE_FETCHED', 'Workspace retrieved successfully.', {\n\t\t\t\tworkspace: toWorkspaceDTO(ctx.workspace as IWorkspace),\n\t\t\t});\n\t\t},\n\n\t\tasync update(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst opts: Parameters<typeof workspaces.update>[1] = {};\n\n\t\t\tif (typeof body?.['name'] === 'string') opts.name = body['name'].trim();\n\t\t\tif (body?.['description'] !== undefined)\n\t\t\t\topts.description = typeof body['description'] === 'string' ? body['description'] : null;\n\t\t\tif (body?.['motto'] !== undefined)\n\t\t\t\topts.motto = typeof body['motto'] === 'string' ? body['motto'] : null;\n\t\t\tif (body?.['phone'] !== undefined)\n\t\t\t\topts.phone = typeof body['phone'] === 'string' ? body['phone'].trim() : null;\n\t\t\tif (body?.['businessType'] !== undefined)\n\t\t\t\topts.businessType = typeof body['businessType'] === 'string' ? body['businessType'] : null;\n\t\t\tif (body?.['address'] !== undefined && typeof body['address'] === 'object')\n\t\t\t\topts.address = (body['address'] ?? null) as NonNullable<\n\t\t\t\t\tParameters<typeof workspaces.update>[1]\n\t\t\t\t>['address'] & (object | null);\n\n\t\t\tconst workspace = await workspaces.update(ctx.workspace.id, opts);\n\t\t\tif (!workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\treturn setApiResponse(HTTP.OK, 'WORKSPACE_UPDATED', 'Workspace updated successfully.', {\n\t\t\t\tworkspace: toWorkspaceDTO(workspace),\n\t\t\t});\n\t\t},\n\n\t\tasync archive(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\tif (ctx.workspace.isPersonal) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.FORBIDDEN,\n\t\t\t\t\t'FORBIDDEN',\n\t\t\t\t\t'Personal workspaces cannot be archived',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait workspaces.archive(ctx.workspace.id, ctx.user!.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'WORKSPACE_ARCHIVED', 'Workspace archived successfully.');\n\t\t},\n\n\t\tasync restore(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tawait workspaces.restore(ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'WORKSPACE_RESTORED', 'Workspace restored successfully.');\n\t\t},\n\n\t\tasync getSettings(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst settings = await workspaces.getSettings(ctx.workspace.id);\n\t\t\treturn setApiResponse(\n\t\t\t\tHTTP.OK,\n\t\t\t\t'SETTINGS_FETCHED',\n\t\t\t\t'Workspace settings retrieved successfully.',\n\t\t\t\t{\n\t\t\t\t\tsettings: toSettingsDTO(settings),\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\n\t\tasync updateSettings(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tif (!body || Object.keys(body).length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'No settings provided');\n\t\t\t}\n\n\t\t\tconst patch: Record<string, string> = {};\n\t\t\tfor (const key of ['locale', 'timezone', 'currency', 'dateFormat', 'timeFormat'] as const) {\n\t\t\t\tif (typeof body[key] === 'string') patch[key] = body[key] as string;\n\t\t\t}\n\n\t\t\tconst settings = await workspaces.updateSettings(ctx.workspace.id, patch);\n\t\t\treturn setApiResponse(\n\t\t\t\tHTTP.OK,\n\t\t\t\t'SETTINGS_UPDATED',\n\t\t\t\t'Workspace settings updated successfully.',\n\t\t\t\t{\n\t\t\t\t\tsettings: toSettingsDTO(settings),\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t};\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWorkspace, IWorkspaceSettings } from '../types';\nimport {\n\tfindWorkspaceById,\n\tfindWorkspacesByUserId,\n\tcreateWorkspace,\n\tcreatePersonalWorkspace,\n\tfindPersonalWorkspace,\n\tupdateWorkspace,\n\tarchiveWorkspace,\n\trestoreWorkspace,\n\tgetWorkspaceSettings,\n\tupdateWorkspaceSettings,\n} from '../services/workspaces';\n\nexport class WorkspaceModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tfindById(id: string): Promise<IWorkspace | null> {\n\t\treturn findWorkspaceById(id, this.store);\n\t}\n\n\tfindByUserId(userId: string): Promise<IWorkspace[]> {\n\t\treturn findWorkspacesByUserId(userId, this.store);\n\t}\n\n\tcreate(opts: Parameters<typeof createWorkspace>[0]): Promise<IWorkspace> {\n\t\treturn createWorkspace(opts, this.store);\n\t}\n\n\tcreatePersonal(opts: Parameters<typeof createPersonalWorkspace>[0]): Promise<IWorkspace | null> {\n\t\treturn createPersonalWorkspace(opts, this.store);\n\t}\n\n\tfindPersonal(userId: string): Promise<IWorkspace | null> {\n\t\treturn findPersonalWorkspace(userId, this.store);\n\t}\n\n\tupdate(id: string, opts: Parameters<typeof updateWorkspace>[1]): Promise<IWorkspace | null> {\n\t\treturn updateWorkspace(id, opts, this.store);\n\t}\n\n\tarchive(id: string, byUser: string): Promise<void> {\n\t\treturn archiveWorkspace(id, byUser, this.store);\n\t}\n\n\trestore(id: string): Promise<void> {\n\t\treturn restoreWorkspace(id, this.store);\n\t}\n\n\tgetSettings(id: string): Promise<IWorkspaceSettings> {\n\t\treturn getWorkspaceSettings(id, this.store);\n\t}\n\n\tupdateSettings(id: string, settings: Partial<IWorkspaceSettings>): Promise<IWorkspaceSettings> {\n\t\treturn updateWorkspaceSettings(id, settings, this.store);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IMember, IRole } from '../types';\nimport {\n\tgetMember,\n\tlistMembers,\n\taddMember,\n\tremoveMember,\n\tgetUserRoles,\n\taddRoleToMember,\n\tremoveRoleFromMember,\n} from '../services/members';\n\nexport class MemberModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tget(userId: string, workspaceId: string): Promise<IMember | null> {\n\t\treturn getMember(userId, workspaceId, this.store);\n\t}\n\n\tlist(workspaceId: string): Promise<IMember[]> {\n\t\treturn listMembers(workspaceId, this.store);\n\t}\n\n\tadd(opts: Parameters<typeof addMember>[0]): Promise<void> {\n\t\treturn addMember(opts, this.store);\n\t}\n\n\tremove(userId: string, workspaceId: string): Promise<void> {\n\t\treturn removeMember(userId, workspaceId, this.store);\n\t}\n\n\tgetUserRoles(userId: string, workspaceId: string): Promise<IRole[]> {\n\t\treturn getUserRoles(userId, workspaceId, this.store);\n\t}\n\n\taddRole(userId: string, workspaceId: string, roleId: string): Promise<void> {\n\t\treturn addRoleToMember(userId, workspaceId, roleId, this.store);\n\t}\n\n\tremoveRole(userId: string, workspaceId: string, roleId: string): Promise<void> {\n\t\treturn removeRoleFromMember(userId, workspaceId, roleId, this.store);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IRole } from '../types';\n\nconst SELECT_ROLE = `\n\tid,\n\tname,\n\tis_system AS \"isSystem\",\n\tactive,\n\tdescription,\n\tworkspace_id AS \"workspaceId\"\n`;\n\nexport async function createRole(\n\topts: { name: string; workspaceId: string; description?: string },\n\tstore: IStoreAdapter,\n): Promise<IRole> {\n\tconst [role] = await store.query<IRole>(\n\t\t`INSERT INTO fonderie_roles (name, workspace_id, description)\n\t\t VALUES ($1, $2, $3)\n\t\t RETURNING ${SELECT_ROLE}`,\n\t\t[opts.name, opts.workspaceId, opts.description ?? null],\n\t);\n\tif (!role) throw new Error('Failed to create role');\n\treturn role;\n}\n\nexport async function findSystemRole(name: string, store: IStoreAdapter): Promise<IRole | null> {\n\tconst [row] = await store.query<IRole>(\n\t\t`SELECT ${SELECT_ROLE} FROM fonderie_roles WHERE name = $1 AND is_system = true LIMIT 1`,\n\t\t[name],\n\t);\n\treturn row ?? null;\n}\n\nexport async function getRoleById(id: string, store: IStoreAdapter): Promise<IRole | null> {\n\tconst [row] = await store.query<IRole>(\n\t\t`SELECT ${SELECT_ROLE} FROM fonderie_roles WHERE id = $1`,\n\t\t[id],\n\t);\n\treturn row ?? null;\n}\n\nexport async function listWorkspaceRoles(\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<IRole[]> {\n\treturn store.query<IRole>(\n\t\t`SELECT ${SELECT_ROLE}\n\t\t FROM fonderie_roles\n\t\t WHERE workspace_id = $1 OR is_system = true\n\t\t ORDER BY is_system DESC, name ASC`,\n\t\t[workspaceId],\n\t);\n}\n\nexport async function updateRole(\n\tid: string,\n\topts: { name?: string; description?: string | null; active?: boolean },\n\tstore: IStoreAdapter,\n): Promise<IRole | null> {\n\tconst sets: string[] = [];\n\tconst params: unknown[] = [id];\n\n\tif (opts.name !== undefined) {\n\t\tparams.push(opts.name);\n\t\tsets.push(`name = $${params.length}`);\n\t}\n\tif (opts.description !== undefined) {\n\t\tparams.push(opts.description);\n\t\tsets.push(`description = $${params.length}`);\n\t}\n\tif (opts.active !== undefined) {\n\t\tparams.push(opts.active);\n\t\tsets.push(`active = $${params.length}`);\n\t}\n\n\tif (sets.length === 0) return getRoleById(id, store);\n\n\tconst [row] = await store.query<IRole>(\n\t\t`UPDATE fonderie_roles\n\t\t SET ${sets.join(', ')}\n\t\t WHERE id = $1 AND is_system = false\n\t\t RETURNING ${SELECT_ROLE}`,\n\t\tparams,\n\t);\n\treturn row ?? null;\n}\n\nexport async function deleteRole(\n\tid: string,\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`DELETE FROM fonderie_roles\n\t\t WHERE id = $1 AND workspace_id = $2 AND is_system = false`,\n\t\t[id, workspaceId],\n\t);\n}\n\nexport async function setRolePermissions(\n\troleId: string,\n\tworkspaceId: string,\n\tpermissions: Array<{\n\t\tpermissionKey: string;\n\t\tcanCreate: boolean;\n\t\tcanRead: boolean;\n\t\tcanUpdate: boolean;\n\t\tcanDelete: boolean;\n\t}>,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tif (permissions.length === 0) {\n\t\tawait store.query(\n\t\t\t`DELETE FROM fonderie_role_permissions WHERE role_id = $1 AND workspace_id = $2`,\n\t\t\t[roleId, workspaceId],\n\t\t);\n\t\treturn;\n\t}\n\n\tawait store.transaction(async (tx) => {\n\t\tawait tx.query(\n\t\t\t`DELETE FROM fonderie_role_permissions WHERE role_id = $1 AND workspace_id = $2`,\n\t\t\t[roleId, workspaceId],\n\t\t);\n\n\t\tfor (const p of permissions) {\n\t\t\tawait tx.query(\n\t\t\t\t`INSERT INTO fonderie_role_permissions\n\t\t\t\t (role_id, workspace_id, permission_key, can_create, can_read, can_update, can_delete)\n\t\t\t\t VALUES ($1, $2, $3, $4, $5, $6, $7)\n\t\t\t\t ON CONFLICT (role_id, permission_key)\n\t\t\t\t DO UPDATE SET\n\t\t\t\t can_create = $4, can_read = $5,\n\t\t\t\t can_update = $6, can_delete = $7`,\n\t\t\t\t[roleId, workspaceId, p.permissionKey, p.canCreate, p.canRead, p.canUpdate, p.canDelete],\n\t\t\t);\n\t\t}\n\t});\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IRole } from '../types';\nimport {\n\tcreateRole,\n\tfindSystemRole,\n\tgetRoleById,\n\tlistWorkspaceRoles,\n\tupdateRole,\n\tdeleteRole,\n\tsetRolePermissions,\n} from '../services/roles';\n\nexport class RoleModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tcreate(opts: Parameters<typeof createRole>[0]): Promise<IRole> {\n\t\treturn createRole(opts, this.store);\n\t}\n\n\tfindSystem(name: string): Promise<IRole | null> {\n\t\treturn findSystemRole(name, this.store);\n\t}\n\n\tfindById(id: string): Promise<IRole | null> {\n\t\treturn getRoleById(id, this.store);\n\t}\n\n\tlist(workspaceId: string): Promise<IRole[]> {\n\t\treturn listWorkspaceRoles(workspaceId, this.store);\n\t}\n\n\tupdate(id: string, opts: Parameters<typeof updateRole>[1]): Promise<IRole | null> {\n\t\treturn updateRole(id, opts, this.store);\n\t}\n\n\tdelete(id: string, workspaceId: string): Promise<void> {\n\t\treturn deleteRole(id, workspaceId, this.store);\n\t}\n\n\tsetPermissions(\n\t\troleId: string,\n\t\tworkspaceId: string,\n\t\tpermissions: Parameters<typeof setRolePermissions>[2],\n\t): Promise<void> {\n\t\treturn setRolePermissions(roleId, workspaceId, permissions, this.store);\n\t}\n}\n","import { stringOrEmpty, booleanOrFalse } from '@fonderie/core/parser';\n\nimport type { IWorkspace, IRole, IMember, IInvitation, IWorkspaceSettings } from '../types';\n\nexport interface IWorkspaceAddressDTO {\n\tline1: string;\n\tline2: string;\n\tcity: string;\n\tstate: string;\n\tzip: string;\n\tcountry: string;\n}\n\nexport interface IWorkspaceDTO {\n\tid: string;\n\tname: string;\n\tslug: string;\n\ttype: string;\n\tdescription: string;\n\tmotto: string;\n\tphone: string;\n\tbusinessType: string;\n\taddress: IWorkspaceAddressDTO;\n\tplan: string;\n\townerId: string;\n\tisPersonal: boolean;\n\tisArchived: boolean;\n\tarchivedAt: string;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface IRoleDTO {\n\tid: string;\n\tname: string;\n\tisSystem: boolean;\n\tactive: boolean;\n\tdescription: string;\n\tworkspaceId: string;\n}\n\nexport interface IMemberDTO {\n\tuserId: string;\n\tworkspaceId: string;\n\troleId: string;\n\troleName: string;\n\tconfirmed: boolean;\n\tcreatedAt: string;\n}\n\nexport interface IInvitationDTO {\n\tid: string;\n\tworkspaceId: string;\n\temail: string;\n\troleId: string;\n\ttoken: string;\n\tstatus: string;\n\texpiresAt: string;\n\tcreatedAt: string;\n}\n\nexport interface IWorkspaceSettingsDTO {\n\tlocale: string;\n\ttimezone: string;\n\tcurrency: string;\n\tdateFormat: string;\n\ttimeFormat: string;\n}\n\nexport function toWorkspaceDTO(ws: IWorkspace): IWorkspaceDTO {\n\tconst addr = ws.address ?? {};\n\treturn {\n\t\tid: stringOrEmpty(ws.id),\n\t\tname: stringOrEmpty(ws.name),\n\t\tslug: stringOrEmpty(ws.slug),\n\t\ttype: stringOrEmpty(ws.type),\n\t\tdescription: stringOrEmpty(ws.description),\n\t\tmotto: stringOrEmpty(ws.motto),\n\t\tphone: stringOrEmpty(ws.phone),\n\t\tbusinessType: stringOrEmpty(ws.businessType),\n\t\taddress: {\n\t\t\tline1: stringOrEmpty(addr.line1),\n\t\t\tline2: stringOrEmpty(addr.line2),\n\t\t\tcity: stringOrEmpty(addr.city),\n\t\t\tstate: stringOrEmpty(addr.state),\n\t\t\tzip: stringOrEmpty(addr.zip),\n\t\t\tcountry: stringOrEmpty(addr.country),\n\t\t},\n\t\tplan: stringOrEmpty(ws.plan),\n\t\townerId: stringOrEmpty(ws.ownerId),\n\t\tisPersonal: booleanOrFalse(ws.isPersonal),\n\t\tisArchived: ws.archivedAt !== null,\n\t\tarchivedAt: stringOrEmpty(ws.archivedAt),\n\t\tcreatedAt: stringOrEmpty(ws.createdAt),\n\t\tupdatedAt: stringOrEmpty(ws.updatedAt),\n\t};\n}\n\nexport function toRoleDTO(role: IRole): IRoleDTO {\n\treturn {\n\t\tid: stringOrEmpty(role.id),\n\t\tname: stringOrEmpty(role.name),\n\t\tisSystem: booleanOrFalse(role.isSystem),\n\t\tactive: role.active !== false,\n\t\tdescription: stringOrEmpty(role.description),\n\t\tworkspaceId: stringOrEmpty(role.workspaceId),\n\t};\n}\n\nexport function toMemberDTO(m: IMember): IMemberDTO {\n\treturn {\n\t\tuserId: stringOrEmpty(m.userId),\n\t\tworkspaceId: stringOrEmpty(m.workspaceId),\n\t\troleId: stringOrEmpty(m.roleId),\n\t\troleName: stringOrEmpty(m.roleName),\n\t\tconfirmed: booleanOrFalse(m.confirmed),\n\t\tcreatedAt: stringOrEmpty(m.createdAt),\n\t};\n}\n\nexport function toInvitationDTO(inv: IInvitation): IInvitationDTO {\n\treturn {\n\t\tid: stringOrEmpty(inv.id),\n\t\tworkspaceId: stringOrEmpty(inv.workspaceId),\n\t\temail: stringOrEmpty(inv.email),\n\t\troleId: stringOrEmpty(inv.roleId),\n\t\ttoken: stringOrEmpty(inv.token),\n\t\tstatus: stringOrEmpty(inv.status),\n\t\texpiresAt: stringOrEmpty(inv.expiresAt),\n\t\tcreatedAt: stringOrEmpty(inv.createdAt),\n\t};\n}\n\nexport function toSettingsDTO(s: IWorkspaceSettings): IWorkspaceSettingsDTO {\n\treturn {\n\t\tlocale: stringOrEmpty(s.locale),\n\t\ttimezone: stringOrEmpty(s.timezone),\n\t\tcurrency: stringOrEmpty(s.currency),\n\t\tdateFormat: stringOrEmpty(s.dateFormat),\n\t\ttimeFormat: stringOrEmpty(s.timeFormat),\n\t};\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { MemberModel } from '../models/member.model';\nimport { toMemberDTO, toRoleDTO } from '../dtos/workspace';\n\nexport function memberController(store: IStoreAdapter) {\n\tconst members = new MemberModel(store);\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst list = await members.list(ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'MEMBERS_FETCHED', 'Members retrieved successfully.', {\n\t\t\t\tmembers: list.map(toMemberDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync remove(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\tif (ctx.workspace.isPersonal) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.FORBIDDEN,\n\t\t\t\t\t'FORBIDDEN',\n\t\t\t\t\t'Personal workspaces do not support member management',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst userId = params?.['userId'];\n\n\t\t\tif (!userId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'userId is required');\n\t\t\tif (userId === ctx.user?.id) {\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVALID_OPERATION', 'Cannot remove yourself');\n\t\t\t}\n\n\t\t\tawait members.remove(userId, ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'MEMBER_REMOVED', 'Member removed successfully.');\n\t\t},\n\n\t\tasync getUserRoles(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst userId = params?.['userId'];\n\t\t\tif (!userId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'userId is required');\n\n\t\t\tconst roles = await members.getUserRoles(userId, ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'ROLES_FETCHED', 'Member roles retrieved successfully.', {\n\t\t\t\troles: roles.map(toRoleDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync addRole(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst userId = params?.['userId'];\n\t\t\tconst roleId = (body?.['roleId'] ?? params?.['roleId']) as string | undefined;\n\n\t\t\tif (!userId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'userId is required');\n\t\t\tif (!roleId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'roleId is required');\n\n\t\t\tawait members.addRole(userId, ctx.workspace.id, roleId);\n\t\t\treturn setApiResponse(HTTP.OK, 'ROLE_ASSIGNED', 'Role assigned successfully.');\n\t\t},\n\n\t\tasync removeRole(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst userId = params?.['userId'];\n\t\t\tconst roleId = params?.['roleId'];\n\n\t\t\tif (!userId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'userId is required');\n\t\t\tif (!roleId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'roleId is required');\n\n\t\t\ttry {\n\t\t\t\tawait members.removeRole(userId, ctx.workspace.id, roleId);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'ROLE_REMOVED', 'Role removed successfully.');\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : 'Failed';\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'OPERATION_FAILED', message);\n\t\t\t}\n\t\t},\n\t};\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { RoleModel } from '../models/role.model';\nimport { toRoleDTO } from '../dtos/workspace';\n\nexport function roleController(store: IStoreAdapter) {\n\tconst roles = new RoleModel(store);\n\n\treturn {\n\t\tasync create(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst name = body?.['name'];\n\t\t\tconst description = body?.['description'];\n\n\t\t\tif (typeof name !== 'string' || name.trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'name is required');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst opts: Parameters<typeof roles.create>[0] = {\n\t\t\t\t\tname: name.trim(),\n\t\t\t\t\tworkspaceId: ctx.workspace.id,\n\t\t\t\t};\n\n\t\t\t\tif (typeof description === 'string') {\n\t\t\t\t\topts.description = description;\n\t\t\t\t}\n\n\t\t\t\tconst role = await roles.create(opts);\n\t\t\t\treturn setApiResponse(HTTP.CREATED, 'ROLE_CREATED', 'Role created successfully.', {\n\t\t\t\t\trole: toRoleDTO(role),\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : 'Failed to create role';\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'OPERATION_FAILED', message);\n\t\t\t}\n\t\t},\n\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\t}\n\n\t\t\tconst list = await roles.list(ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'ROLES_FETCHED', 'Roles retrieved successfully.', {\n\t\t\t\troles: list.map(toRoleDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync get(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst roleId = params?.['roleId'];\n\t\t\tif (!roleId) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'roleId is required');\n\t\t\t}\n\n\t\t\tconst role = await roles.findById(roleId);\n\t\t\tif (!role) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Role not found');\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.OK, 'ROLE_FETCHED', 'Role retrieved successfully.', {\n\t\t\t\trole: toRoleDTO(role),\n\t\t\t});\n\t\t},\n\n\t\tasync update(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst roleId = params?.['roleId'];\n\t\t\tif (!roleId) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'roleId is required');\n\t\t\t}\n\n\t\t\tconst opts: { name?: string; description?: string | null; active?: boolean } = {};\n\n\t\t\tif (typeof body?.['name'] === 'string') {\n\t\t\t\topts.name = body['name'];\n\t\t\t}\n\n\t\t\tif (typeof body?.['description'] === 'string') {\n\t\t\t\topts.description = body['description'];\n\t\t\t}\n\n\t\t\tif (body?.['description'] === null) {\n\t\t\t\topts.description = null;\n\t\t\t}\n\n\t\t\tif (typeof body?.['active'] === 'boolean') {\n\t\t\t\topts.active = body['active'];\n\t\t\t}\n\n\t\t\tconst role = await roles.update(roleId, opts);\n\t\t\tif (!role) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Role not found or is a system role');\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.OK, 'ROLE_UPDATED', 'Role updated successfully.', {\n\t\t\t\trole: toRoleDTO(role),\n\t\t\t});\n\t\t},\n\n\t\tasync remove(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst roleId = params?.['roleId'];\n\t\t\tif (!roleId) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'roleId is required');\n\t\t\t}\n\n\t\t\tawait roles.delete(roleId, ctx.workspace.id);\n\n\t\t\treturn setApiResponse(HTTP.OK, 'ROLE_DELETED', 'Role deleted successfully.');\n\t\t},\n\n\t\tasync setPermissions(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst roleId = params?.['roleId'];\n\t\t\tif (!roleId) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'roleId is required');\n\t\t\t}\n\n\t\t\tconst perms = body?.['permissions'];\n\t\t\tif (!Array.isArray(perms)) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t'permissions array is required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst normalized = perms\n\t\t\t\t.map((p: unknown) => {\n\t\t\t\t\tconst perm = p as Record<string, unknown>;\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpermissionKey: String(perm['permissionKey'] ?? ''),\n\t\t\t\t\t\tcanCreate: Boolean(perm['canCreate']),\n\t\t\t\t\t\tcanRead: Boolean(perm['canRead']),\n\t\t\t\t\t\tcanUpdate: Boolean(perm['canUpdate']),\n\t\t\t\t\t\tcanDelete: Boolean(perm['canDelete']),\n\t\t\t\t\t};\n\t\t\t\t})\n\t\t\t\t.filter((p) => p.permissionKey.length > 0);\n\n\t\t\tawait roles.setPermissions(roleId, ctx.workspace.id, normalized);\n\n\t\t\treturn setApiResponse(HTTP.OK, 'PERMISSIONS_SET', 'Role permissions updated successfully.');\n\t\t},\n\t};\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { IFonderieContext } from '@fonderie/core';\nimport type { ICourierMessage } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport type { EventBus } from '@fonderie/events';\nimport { NOTIFICATION_EVENT } from '@fonderie/events';\n\nimport { MESSAGE_KEYS } from '../config';\nimport { InvitationModel } from '../models/invitation.model';\nimport { toInvitationDTO } from '../dtos/workspace';\n\n// Seat limits are OPTIONAL and owned by @fonderie/billing. Per Fonderie's\n// architecture law (packages talk only through ctx.meta — no sibling imports),\n// we read billing's cached context directly instead of importing its code, so\n// workspaces has no build- or runtime-dependency on billing. Billing registered\n// → it caches an IBillingContext on ctx.meta['billing'] and seats are enforced;\n// billing absent → fail open (no limit), which is the intended default.\nfunction seatLimitFromMeta(ctx: IFonderieContext): number | null {\n\tconst billing = ctx.meta['billing'] as\n\t\t| { statuses?: Record<string, { type?: string; limit?: number }> }\n\t\t| undefined;\n\tconst status = billing?.statuses?.['seats'];\n\tif (!status || status.type === 'feature' || typeof status.limit !== 'number') return null;\n\treturn status.limit;\n}\n\nexport function invitationController(store: IStoreAdapter, ttl: string, bus?: EventBus) {\n\tconst invitations = new InvitationModel(store);\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst list = await invitations.list(ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'INVITATIONS_FETCHED', 'Invitations retrieved successfully.', {\n\t\t\t\tinvitations: list.map(toInvitationDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync invite(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\t\t\tif (ctx.workspace.isPersonal) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.FORBIDDEN,\n\t\t\t\t\t'FORBIDDEN',\n\t\t\t\t\t'Personal workspaces do not support invitations',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as unknown;\n\t\t\tconst entries = Array.isArray(body) ? body as Record<string, unknown>[] : [body as Record<string, unknown>];\n\n\t\t\tif (!entries.length) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'at least one invite is required');\n\t\t\t}\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (typeof entry?.['email'] !== 'string') {\n\t\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'email is required for every invite');\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Seat limit — enforced automatically when the billing module is\n\t\t\t// registered (read from ctx.meta['billing']); fail-open when absent.\n\t\t\tconst seatLimit = seatLimitFromMeta(ctx);\n\t\t\tif (seatLimit !== null) {\n\t\t\t\tconst [countRow] = await store.query<{ count: string }>(\n\t\t\t\t\t`SELECT COUNT(*) AS count FROM fonderie_role_user_workspaces\n\t\t\t\t\t WHERE workspace_id = $1 AND removed = false AND suspended = false`,\n\t\t\t\t\t[ctx.workspace.id],\n\t\t\t\t);\n\t\t\t\tconst total = parseInt(countRow!.count, 10);\n\t\t\t\tconst occupied = ctx.workspace.isPersonal ? total : Math.max(0, total - 1);\n\t\t\t\tif (occupied + entries.length > seatLimit) {\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.PAYMENT_REQUIRED,\n\t\t\t\t\t\t'SEAT_LIMIT_REACHED',\n\t\t\t\t\t\t`Your plan allows ${seatLimit} seat${seatLimit === 1 ? '' : 's'}. Upgrade to invite more members.`,\n\t\t\t\t\t\t{ limit: seatLimit },\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Resolve default role once for entries that omit roleId.\n\t\t\t// Invitations must never default to a privileged role: fall back to\n\t\t\t// the seeded system GUEST role (least privilege). Granting anything\n\t\t\t// more requires an explicit roleId from GET /workspaces/roles.\n\t\t\tlet defaultRoleId: string | undefined;\n\t\t\tconst needsDefault = entries.some((e) => !e['roleId']);\n\t\t\tif (needsDefault) {\n\t\t\t\tconst [row] = await store.query<{ id: string }>(\n\t\t\t\t\t`SELECT id FROM fonderie_roles\n\t\t\t\t\t WHERE name = 'GUEST' AND workspace_id IS NULL AND is_system = true\n\t\t\t\t\t LIMIT 1`,\n\t\t\t\t);\n\t\t\t\tdefaultRoleId = row?.id;\n\t\t\t\tif (!defaultRoleId) {\n\t\t\t\t\treturn setApiResponse(HTTP.SERVER_ERROR, 'SERVER_ERROR', 'Default role not found');\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst results = await Promise.all(\n\t\t\t\tentries.map(async (entry) => {\n\t\t\t\t\tconst email = entry['email'] as string;\n\t\t\t\t\tconst resolvedRoleId = (entry['roleId'] as string | undefined) ?? defaultRoleId!;\n\n\t\t\t\t\tconst invitation = await invitations.create({\n\t\t\t\t\t\tworkspaceId: ctx.workspace!.id,\n\t\t\t\t\t\temail,\n\t\t\t\t\t\troleId: resolvedRoleId,\n\t\t\t\t\t\tttl,\n\t\t\t\t\t});\n\n\t\t\t\t\tbus\n\t\t\t\t\t\t?.emit(NOTIFICATION_EVENT, {\n\t\t\t\t\t\t\ttype: MESSAGE_KEYS.workspaceInvitation,\n\t\t\t\t\t\t\trecipient: { email, phone: null, deviceToken: null },\n\t\t\t\t\t\t\tdata: { token: invitation.token, pin: invitation.pin },\n\t\t\t\t\t\t} satisfies ICourierMessage)\n\t\t\t\t\t\t.catch(() => {});\n\n\t\t\t\t\treturn { invitationId: invitation.id, email };\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn setApiResponse(HTTP.CREATED, 'INVITATIONS_SENT', 'Invitations sent successfully.', {\n\t\t\t\tinvitations: results,\n\t\t\t});\n\t\t},\n\n\t\tasync cancel(ctx: IFonderieContext): Promise<Response> {\n\t\t\tif (!ctx.workspace) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Workspace not found');\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst invitationId = params?.['inviteId'];\n\n\t\t\tif (!invitationId)\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'inviteId is required');\n\n\t\t\tawait invitations.cancel(invitationId, ctx.workspace.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'INVITATION_CANCELLED', 'Invitation cancelled successfully.');\n\t\t},\n\n\t\tasync accept(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst pin = body?.['pin'];\n\n\t\t\tif (typeof pin !== 'string') {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'pin is required');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { workspaceId } = await invitations.acceptByPin({ pin, userId: ctx.user!.id });\n\t\t\t\treturn setApiResponse(HTTP.OK, 'INVITATION_ACCEPTED', 'Invitation accepted successfully.', {\n\t\t\t\t\tworkspaceId,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : 'Invalid invitation';\n\t\t\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVITATION_FAILED', message);\n\t\t\t}\n\t\t},\n\t};\n}\n","import { randomBytes } from 'node:crypto';\n\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IInvitation } from '../types';\n\nfunction generateToken(): string {\n\treturn randomBytes(32).toString('hex');\n}\n\nfunction generatePin(): string {\n\treturn Math.floor(100000 + Math.random() * 900000).toString();\n}\n\nfunction parseTtl(ttl: string): number {\n\tconst units: Record<string, number> = {\n\t\ts: 1000,\n\t\tm: 60_000,\n\t\th: 3_600_000,\n\t\td: 86_400_000,\n\t};\n\tconst match = ttl.match(/^(\\d+)([smhd])$/);\n\tif (!match) return 7 * 86_400_000;\n\tconst [, n, unit] = match;\n\treturn parseInt(n!, 10) * (units[unit!] ?? 0);\n}\n\nconst SELECT_INV = `\n\tid,\n\tworkspace_id AS \"workspaceId\",\n\temail,\n\trole_id AS \"roleId\",\n\ttoken,\n\tpin,\n\tstatus,\n\texpires_at AS \"expiresAt\",\n\tcreated_at AS \"createdAt\"\n`;\n\nexport async function createInvitation(\n\topts: { workspaceId: string; email: string; roleId: string; ttl?: string },\n\tstore: IStoreAdapter,\n): Promise<IInvitation> {\n\tconst token = generateToken();\n\tconst pin = generatePin();\n\tconst expiresAt = new Date(Date.now() + parseTtl(opts.ttl ?? '7d'));\n\n\tconst [invitation] = await store.query<IInvitation>(\n\t\t`INSERT INTO fonderie_workspace_invitations\n\t\t (workspace_id, email, role_id, token, pin, expires_at)\n\t\t VALUES ($1, $2, $3, $4, $5, $6)\n\t\t ON CONFLICT DO NOTHING\n\t\t RETURNING ${SELECT_INV}`,\n\t\t[opts.workspaceId, opts.email, opts.roleId, token, pin, expiresAt],\n\t);\n\n\tif (!invitation) {\n\t\t// Already pending — update it\n\t\tconst [updated] = await store.query<IInvitation>(\n\t\t\t`UPDATE fonderie_workspace_invitations\n\t\t\t SET token = $4, pin = $5, expires_at = $6, role_id = $3, status = 'PENDING'\n\t\t\t WHERE workspace_id = $1 AND email = $2 AND status = 'PENDING'\n\t\t\t RETURNING ${SELECT_INV}`,\n\t\t\t[opts.workspaceId, opts.email, opts.roleId, token, pin, expiresAt],\n\t\t);\n\t\tif (!updated) throw new Error('Failed to create invitation');\n\t\treturn updated;\n\t}\n\n\treturn invitation;\n}\n\nexport async function listInvitations(\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<IInvitation[]> {\n\treturn store.query<IInvitation>(\n\t\t`SELECT ${SELECT_INV}\n\t\t FROM fonderie_workspace_invitations\n\t\t WHERE workspace_id = $1 AND status = 'PENDING'\n\t\t ORDER BY created_at DESC`,\n\t\t[workspaceId],\n\t);\n}\n\nexport async function cancelInvitation(\n\tinvitationId: string,\n\tworkspaceId: string,\n\tstore: IStoreAdapter,\n): Promise<void> {\n\tawait store.query(\n\t\t`UPDATE fonderie_workspace_invitations\n\t\t SET status = 'CANCELLED'\n\t\t WHERE id = $1 AND workspace_id = $2 AND status = 'PENDING'`,\n\t\t[invitationId, workspaceId],\n\t);\n}\n\nexport async function acceptInvitationByPin(\n\topts: { pin: string; userId: string },\n\tstore: IStoreAdapter,\n): Promise<{ workspaceId: string; roleId: string }> {\n\tconst [inv] = await store.query<{\n\t\tid: string;\n\t\tworkspaceId: string;\n\t\troleId: string;\n\t\texpiresAt: string;\n\t}>(\n\t\t`SELECT id, workspace_id AS \"workspaceId\", role_id AS \"roleId\", expires_at AS \"expiresAt\"\n\t\t FROM fonderie_workspace_invitations\n\t\t WHERE pin = $1 AND status = 'PENDING'`,\n\t\t[opts.pin],\n\t);\n\n\tif (!inv) throw new Error('Invalid PIN');\n\tif (new Date() > new Date(inv.expiresAt)) throw new Error('Invitation expired');\n\n\tawait store.transaction(async (tx) => {\n\t\tawait Promise.all([\n\t\t\ttx.query(\n\t\t\t\t`INSERT INTO fonderie_role_user_workspaces (user_id, workspace_id, role_id, confirmed)\n\t\t\t\t VALUES ($1, $2, $3, true)\n\t\t\t\t ON CONFLICT (user_id, workspace_id, role_id) DO UPDATE\n\t\t\t\t SET confirmed = true, removed = false`,\n\t\t\t\t[opts.userId, inv.workspaceId, inv.roleId],\n\t\t\t),\n\t\t\ttx.query(`UPDATE fonderie_workspace_invitations SET status = 'ACCEPTED' WHERE id = $1`, [\n\t\t\t\tinv.id,\n\t\t\t]),\n\t\t]);\n\t});\n\n\treturn { workspaceId: inv.workspaceId, roleId: inv.roleId };\n}\n\nexport async function acceptInvitationByToken(\n\ttoken: string,\n\tuserId: string,\n\tstore: IStoreAdapter,\n): Promise<{ workspaceId: string; roleId: string }> {\n\tconst [inv] = await store.query<{\n\t\tid: string;\n\t\tworkspaceId: string;\n\t\troleId: string;\n\t\texpiresAt: string;\n\t}>(\n\t\t`SELECT id, workspace_id AS \"workspaceId\", role_id AS \"roleId\", expires_at AS \"expiresAt\"\n\t\t FROM fonderie_workspace_invitations\n\t\t WHERE token = $1 AND status = 'PENDING'`,\n\t\t[token],\n\t);\n\n\tif (!inv) throw new Error('Invalid token');\n\tif (new Date() > new Date(inv.expiresAt)) throw new Error('Invitation expired');\n\n\tawait store.transaction(async (tx) => {\n\t\tawait Promise.all([\n\t\t\ttx.query(\n\t\t\t\t`INSERT INTO fonderie_role_user_workspaces (user_id, workspace_id, role_id, confirmed)\n\t\t\t\t VALUES ($1, $2, $3, true)\n\t\t\t\t ON CONFLICT (user_id, workspace_id, role_id) DO UPDATE\n\t\t\t\t SET confirmed = true, removed = false`,\n\t\t\t\t[userId, inv.workspaceId, inv.roleId],\n\t\t\t),\n\t\t\ttx.query(`UPDATE fonderie_workspace_invitations SET status = 'ACCEPTED' WHERE id = $1`, [\n\t\t\t\tinv.id,\n\t\t\t]),\n\t\t]);\n\t});\n\n\treturn { workspaceId: inv.workspaceId, roleId: inv.roleId };\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IInvitation } from '../types';\nimport {\n\tcreateInvitation,\n\tlistInvitations,\n\tcancelInvitation,\n\tacceptInvitationByPin,\n} from '../services/invitations';\n\nexport class InvitationModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tcreate(opts: Parameters<typeof createInvitation>[0]): Promise<IInvitation> {\n\t\treturn createInvitation(opts, this.store);\n\t}\n\n\tlist(workspaceId: string): Promise<IInvitation[]> {\n\t\treturn listInvitations(workspaceId, this.store);\n\t}\n\n\tcancel(invitationId: string, workspaceId: string): Promise<void> {\n\t\treturn cancelInvitation(invitationId, workspaceId, this.store);\n\t}\n\n\tacceptByPin(\n\t\topts: Parameters<typeof acceptInvitationByPin>[0],\n\t): Promise<{ workspaceId: string; roleId: string }> {\n\t\treturn acceptInvitationByPin(opts, this.store);\n\t}\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport type { EventBus } from '@fonderie/events';\n\nimport type { IWorkspacesConfig } from './config';\nimport { EVENT_KEYS } from './config';\nimport { buildWorkspaceRoutes } from './routes';\nimport { WorkspaceModel } from './models/workspace.model';\nimport { RoleModel } from './models/role.model';\nimport { MemberModel } from './models/member.model';\n\n// Mirror of @fonderie/auth EVENT_KEYS.userRegistered — avoids a runtime\n// dependency on the auth package while remaining explicit about the contract.\nconst AUTH_USER_REGISTERED = 'fonderie.user.registered' as const;\n\ninterface UserRegisteredPayload {\n\tuserId: string;\n}\n\nexport class WorkspacesModule implements IFonderieModule {\n\treadonly name = '@fonderie/workspaces';\n\t// billing is OPTIONAL — seat limits activate if it's registered (read via\n\t// ctx.meta['billing']), otherwise invitations are unlimited. Not a hard dep.\n\treadonly deps = ['@fonderie/auth'];\n\n\tconstructor(\n\t\tprivate store: IStoreAdapter,\n\t\tprivate config: IWorkspacesConfig = {},\n\t\tprivate bus?: EventBus,\n\t) {}\n\n\tinstall(app: IFonderieApp): void {\n\t\tif (this.bus && this.config.personalWorkspace !== false) {\n\t\t\tthis.bus.on<UserRegisteredPayload>(\n\t\t\t\tAUTH_USER_REGISTERED,\n\t\t\t\t(payload) => this.provisionPersonalWorkspace(payload),\n\t\t\t\t'workspaces',\n\t\t\t);\n\t\t}\n\n\t\tconst routes = buildWorkspaceRoutes(this.store, this.config, this.bus);\n\t\tfor (const [method, path, ...handlers] of routes) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n\n\tprivate async provisionPersonalWorkspace(payload: UserRegisteredPayload): Promise<void> {\n\t\tconst name = 'My Workspace';\n\t\tconst slug = `${payload.userId}-personal`;\n\n\t\tlet workspaceId: string | undefined;\n\n\t\tawait this.store.transaction(async (tx) => {\n\t\t\tconst wsModel = new WorkspaceModel(tx);\n\t\t\tconst roleModel = new RoleModel(tx);\n\t\t\tconst memModel = new MemberModel(tx);\n\n\t\t\tconst ws = await wsModel.createPersonal({ name, slug, ownerId: payload.userId });\n\t\t\tif (!ws) return; // already exists — idempotent replay\n\n\t\t\tworkspaceId = ws.id;\n\n\t\t\tconst adminRole = await roleModel.findSystem('ADMIN');\n\t\t\tif (!adminRole) throw new Error('System ADMIN role not found');\n\n\t\t\tawait memModel.add({\n\t\t\t\tuserId: payload.userId,\n\t\t\t\tworkspaceId: ws.id,\n\t\t\t\troleId: adminRole.id,\n\t\t\t\tconfirmed: true,\n\t\t\t});\n\t\t});\n\n\t\tif (workspaceId) {\n\t\t\tthis.bus\n\t\t\t\t?.emit(EVENT_KEYS.personalWorkspaceCreated, {\n\t\t\t\t\tworkspaceId,\n\t\t\t\t\tuserId: payload.userId,\n\t\t\t\t})\n\t\t\t\t.catch(() => {});\n\t\t}\n\t}\n}\n","import { setApiResponse, HTTP } from '@fonderie/core';\nimport type { Middleware } from '@fonderie/core';\n\n// Ensures ctx.workspace is set — use after withWorkspace\n\nexport const requireWorkspace: Middleware = async (ctx, next) => {\n\tif (!ctx.workspace) {\n\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'WORKSPACE_REQUIRED', 'Workspace context required');\n\t}\n\treturn next();\n};\n"],"mappings":"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","names":["setApiResponse","HTTP","name","name","setApiResponse","HTTP","name","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","setApiResponse","HTTP","name","setApiResponse","HTTP","setApiResponse","HTTP","email","name","setApiResponse","HTTP"]}
@@ -0,0 +1,85 @@
1
+ -- fonderie_workspaces
2
+ CREATE TABLE IF NOT EXISTS fonderie_workspaces (
3
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
4
+ name TEXT NOT NULL,
5
+ slug TEXT NOT NULL,
6
+ type TEXT NOT NULL DEFAULT 'ORGANIZATION',
7
+ description TEXT,
8
+ plan TEXT NOT NULL DEFAULT 'free',
9
+ owner_id UUID NOT NULL,
10
+ settings JSONB NOT NULL DEFAULT '{}',
11
+ archived_at TIMESTAMPTZ,
12
+ archived_by UUID,
13
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
14
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
15
+ );
16
+
17
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_fw_slug
18
+ ON fonderie_workspaces (slug) WHERE archived_at IS NULL;
19
+
20
+ CREATE INDEX IF NOT EXISTS idx_fw_owner
21
+ ON fonderie_workspaces (owner_id);
22
+
23
+ -- fonderie_roles
24
+ CREATE TABLE IF NOT EXISTS fonderie_roles (
25
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
26
+ name TEXT NOT NULL,
27
+ workspace_id UUID,
28
+ is_system BOOLEAN NOT NULL DEFAULT false,
29
+ active BOOLEAN NOT NULL DEFAULT true,
30
+ description TEXT,
31
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
32
+ );
33
+
34
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_fr_name_ws
35
+ ON fonderie_roles (name, workspace_id) WHERE workspace_id IS NOT NULL;
36
+
37
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_fr_name_system
38
+ ON fonderie_roles (name) WHERE workspace_id IS NULL;
39
+
40
+ CREATE INDEX IF NOT EXISTS idx_fr_workspace
41
+ ON fonderie_roles (workspace_id);
42
+
43
+ -- Seed system roles
44
+ INSERT INTO fonderie_roles (name, workspace_id, is_system, description)
45
+ VALUES
46
+ ('ADMIN', NULL, true, 'System administrator with full access'),
47
+ ('GUEST', NULL, true, 'Guest user with read-only access')
48
+ ON CONFLICT DO NOTHING;
49
+
50
+ -- fonderie_role_user_workspaces: one row per (user, workspace, role) assignment
51
+ CREATE TABLE IF NOT EXISTS fonderie_role_user_workspaces (
52
+ user_id UUID NOT NULL,
53
+ workspace_id UUID NOT NULL,
54
+ role_id UUID NOT NULL,
55
+ confirmed BOOLEAN NOT NULL DEFAULT false,
56
+ removed BOOLEAN NOT NULL DEFAULT false,
57
+ suspended BOOLEAN NOT NULL DEFAULT false,
58
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
59
+ PRIMARY KEY (user_id, workspace_id, role_id)
60
+ );
61
+
62
+ CREATE INDEX IF NOT EXISTS idx_fruw_user_ws
63
+ ON fonderie_role_user_workspaces (user_id, workspace_id);
64
+
65
+ CREATE INDEX IF NOT EXISTS idx_fruw_workspace
66
+ ON fonderie_role_user_workspaces (workspace_id);
67
+
68
+ -- fonderie_workspace_invitations
69
+ CREATE TABLE IF NOT EXISTS fonderie_workspace_invitations (
70
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
71
+ workspace_id UUID NOT NULL,
72
+ email TEXT NOT NULL,
73
+ role_id UUID NOT NULL,
74
+ token TEXT UNIQUE,
75
+ pin TEXT,
76
+ status TEXT NOT NULL DEFAULT 'PENDING',
77
+ expires_at TIMESTAMPTZ NOT NULL,
78
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
79
+ );
80
+
81
+ CREATE INDEX IF NOT EXISTS idx_fwi_ws_email
82
+ ON fonderie_workspace_invitations (workspace_id, email) WHERE status = 'PENDING';
83
+
84
+ CREATE INDEX IF NOT EXISTS idx_fwi_token
85
+ ON fonderie_workspace_invitations (token) WHERE token IS NOT NULL;
@@ -0,0 +1,8 @@
1
+ -- Add is_personal flag to fonderie_workspaces
2
+ ALTER TABLE fonderie_workspaces
3
+ ADD COLUMN IF NOT EXISTS is_personal BOOLEAN NOT NULL DEFAULT false;
4
+
5
+ -- One personal workspace per user — enforced at the DB level
6
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_fw_personal_owner
7
+ ON fonderie_workspaces (owner_id)
8
+ WHERE is_personal = true;
@@ -0,0 +1,5 @@
1
+ ALTER TABLE fonderie_workspaces
2
+ ADD COLUMN IF NOT EXISTS motto TEXT,
3
+ ADD COLUMN IF NOT EXISTS phone TEXT,
4
+ ADD COLUMN IF NOT EXISTS business_type TEXT,
5
+ ADD COLUMN IF NOT EXISTS address JSONB NOT NULL DEFAULT '{}';