mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 06:06:42 +02:00
feat(spaces): validate space metadata on Better Auth organization hooks
Moves the canonical SpaceType + SPACE_MODULE_ALLOWLIST to @mana/shared-types (framework-free) so the Bun services can consume them without pulling in Svelte. shared-branding keeps only the UI-facing labels and descriptions and re-exports the canonical types for frontend convenience. Wires two Better Auth organization hooks in mana-auth: - beforeCreateOrganization asserts metadata.type is a valid SpaceType, rejecting the create with a BAD_REQUEST otherwise. - beforeDeleteOrganization rejects deletion of the personal space. Covered by bun tests (11 assertions) for the helper module. No migration and no schema change — type lives in the existing organization.metadata jsonb column. Plan: docs/plans/spaces-foundation.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9d69e4419d
commit
166d6c6ffb
11 changed files with 683 additions and 331 deletions
|
|
@ -71,12 +71,16 @@ export {
|
|||
export type { AppId, AppBranding, LogoProps, AppLogoWithNameProps } from './types';
|
||||
|
||||
// Spaces (multi-tenancy primitive — see docs/plans/spaces-foundation.md)
|
||||
// Canonical types live in @mana/shared-types; branding adds only UI strings.
|
||||
export {
|
||||
SPACE_TYPES,
|
||||
SPACE_TYPE_LABELS,
|
||||
SPACE_TYPE_DESCRIPTIONS,
|
||||
SPACE_MODULE_ALLOWLIST,
|
||||
isModuleAllowedInSpace,
|
||||
isSpaceType,
|
||||
parseSpaceMetadata,
|
||||
type SpaceType,
|
||||
type SpaceModuleId,
|
||||
type SpaceMetadata,
|
||||
} from './spaces';
|
||||
|
|
|
|||
|
|
@ -1,40 +1,14 @@
|
|||
/**
|
||||
* Space Types & Module Allowlist
|
||||
* Space — UI-facing labels and descriptions
|
||||
*
|
||||
* A "Space" is the unit of data ownership in Mana. Every record belongs to
|
||||
* exactly one Space. Users join Spaces via Better Auth's `member` relation.
|
||||
*
|
||||
* Space = Better Auth Organization with a typed `metadata.type` field. The
|
||||
* type drives which modules are available inside the space (see
|
||||
* `SPACE_MODULE_ALLOWLIST` below).
|
||||
* The canonical type/allowlist definitions live in `@mana/shared-types`
|
||||
* (framework-free so Bun services can import them). This file adds only
|
||||
* the i18n strings that belong to the UI branding layer.
|
||||
*
|
||||
* See docs/plans/spaces-foundation.md for the full RFC.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The six canonical Space types. Every Better Auth organization must have
|
||||
* exactly one of these as `metadata.type`.
|
||||
*
|
||||
* - `personal` — single-member, auto-created on signup. Holds private data
|
||||
* like mood, sleep, dreams that don't belong in a shared context.
|
||||
* - `brand` — external communication identity (e.g. Edisconet, a creator
|
||||
* persona). Hosts social-relay, mail, landing, public content.
|
||||
* - `club` — association/Verein. Member management, dues, events,
|
||||
* governance. Target for the ClubDesk-replacement roadmap.
|
||||
* - `family` — household/family/WG. Shared calendar, shopping, recipes.
|
||||
* - `team` — work team / project. Tasks, chat, docs.
|
||||
* - `practice` — freelancer/solo-business. Invoicing, clients, time tracking.
|
||||
*/
|
||||
export type SpaceType = 'personal' | 'brand' | 'club' | 'family' | 'team' | 'practice';
|
||||
|
||||
export const SPACE_TYPES: readonly SpaceType[] = [
|
||||
'personal',
|
||||
'brand',
|
||||
'club',
|
||||
'family',
|
||||
'team',
|
||||
'practice',
|
||||
] as const;
|
||||
import type { SpaceType } from '@mana/shared-types';
|
||||
|
||||
export const SPACE_TYPE_LABELS = {
|
||||
de: {
|
||||
|
|
@ -53,7 +27,7 @@ export const SPACE_TYPE_LABELS = {
|
|||
team: 'Team',
|
||||
practice: 'Practice',
|
||||
},
|
||||
} as const;
|
||||
} as const satisfies Record<'de' | 'en', Record<SpaceType, string>>;
|
||||
|
||||
export const SPACE_TYPE_DESCRIPTIONS = {
|
||||
de: {
|
||||
|
|
@ -72,161 +46,18 @@ export const SPACE_TYPE_DESCRIPTIONS = {
|
|||
team: 'Work team or project with multiple collaborators.',
|
||||
practice: 'Freelancer or solo business with clients and invoices.',
|
||||
},
|
||||
} as const;
|
||||
} as const satisfies Record<'de' | 'en', Record<SpaceType, string>>;
|
||||
|
||||
/**
|
||||
* Module IDs referenced by the allowlist. Strings (not a strict enum) because
|
||||
* the allowlist intentionally includes modules that don't exist yet — e.g.
|
||||
* `club-finance`, `social-relay` — so features can be gated before the code
|
||||
* lands.
|
||||
*/
|
||||
export type SpaceModuleId = string;
|
||||
|
||||
/**
|
||||
* Which modules are available inside each Space type.
|
||||
*
|
||||
* The personal space gets everything (sentinel `'*'`). Other types get a
|
||||
* curated subset — modules dealing with intimate personal data (mood,
|
||||
* dreams, period, body measurements, …) are intentionally excluded from
|
||||
* shared spaces.
|
||||
*
|
||||
* Rule of thumb: if a module's data would feel wrong shared with co-workers
|
||||
* or club members, keep it out.
|
||||
*/
|
||||
export const SPACE_MODULE_ALLOWLIST: Record<SpaceType, readonly SpaceModuleId[] | '*'> = {
|
||||
personal: '*',
|
||||
|
||||
brand: [
|
||||
'mana',
|
||||
'social-relay', // future — not yet built
|
||||
'mail',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'storage',
|
||||
'uload',
|
||||
'landing', // future
|
||||
'presi',
|
||||
'cards',
|
||||
'picture',
|
||||
'quotes',
|
||||
'news',
|
||||
'news-research',
|
||||
'research-lab',
|
||||
'ai-agents',
|
||||
'companion',
|
||||
'times',
|
||||
'notes',
|
||||
'photos',
|
||||
'invoices',
|
||||
'activity',
|
||||
'goals',
|
||||
],
|
||||
|
||||
club: [
|
||||
'mana',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'events',
|
||||
'mail',
|
||||
'storage',
|
||||
'uload',
|
||||
'news',
|
||||
'research-lab',
|
||||
'club-members', // future — ClubDesk Paket A
|
||||
'club-finance', // future — ClubDesk Paket B
|
||||
'invoices',
|
||||
'finance',
|
||||
'landing', // future — Paket C (Vereinswebsite)
|
||||
'presi',
|
||||
'cards',
|
||||
'quotes',
|
||||
'companion',
|
||||
'times',
|
||||
'notes',
|
||||
'photos',
|
||||
'activity',
|
||||
'goals',
|
||||
],
|
||||
|
||||
family: [
|
||||
'mana',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'events',
|
||||
'mail',
|
||||
'storage',
|
||||
'uload',
|
||||
'recipes',
|
||||
'food',
|
||||
'places',
|
||||
'presi',
|
||||
'cards',
|
||||
'photos',
|
||||
'notes',
|
||||
'companion',
|
||||
'goals',
|
||||
'activity',
|
||||
'wetter',
|
||||
'wisekeep',
|
||||
'firsts',
|
||||
],
|
||||
|
||||
team: [
|
||||
'mana',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'events',
|
||||
'storage',
|
||||
'mail',
|
||||
'uload',
|
||||
'news',
|
||||
'news-research',
|
||||
'research-lab',
|
||||
'presi',
|
||||
'cards',
|
||||
'picture',
|
||||
'notes',
|
||||
'quotes',
|
||||
'invoices',
|
||||
'companion',
|
||||
'ai-agents',
|
||||
'times',
|
||||
'activity',
|
||||
'goals',
|
||||
],
|
||||
|
||||
practice: [
|
||||
'mana',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'storage',
|
||||
'mail',
|
||||
'uload',
|
||||
'invoices',
|
||||
'finance',
|
||||
'times',
|
||||
'notes',
|
||||
'presi',
|
||||
'cards',
|
||||
'quotes',
|
||||
'companion',
|
||||
'research-lab',
|
||||
'activity',
|
||||
'goals',
|
||||
],
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Check whether a module is available inside a given Space type.
|
||||
*
|
||||
* Used by:
|
||||
* - Scope wrapper (apps/mana/.../data/scope/scoped-db.ts) to block queries
|
||||
* against disallowed modules — structural guard against UI bypass.
|
||||
* - UI module launcher to hide disabled modules in the active space.
|
||||
* - Route guards that check before mounting a module page.
|
||||
*/
|
||||
export function isModuleAllowedInSpace(moduleId: SpaceModuleId, spaceType: SpaceType): boolean {
|
||||
const allow = SPACE_MODULE_ALLOWLIST[spaceType];
|
||||
if (allow === '*') return true;
|
||||
return allow.includes(moduleId);
|
||||
}
|
||||
// Re-export canonical types from shared-types so frontend consumers can
|
||||
// import everything space-related from `@mana/shared-branding` for
|
||||
// convenience.
|
||||
export {
|
||||
SPACE_TYPES,
|
||||
SPACE_MODULE_ALLOWLIST,
|
||||
isModuleAllowedInSpace,
|
||||
isSpaceType,
|
||||
parseSpaceMetadata,
|
||||
type SpaceType,
|
||||
type SpaceModuleId,
|
||||
type SpaceMetadata,
|
||||
} from '@mana/shared-types';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue