Skip to main content

Organization Model Migration

Overview

The platform has been refactored from a guild-centric model to an organization-centric model. This migration enables the platform to support multiple types of organizations beyond just WoW guilds, while maintaining backward compatibility through route aliasing.

Architecture Changes

Core Data Model

Before:

  • Guild entity was the primary organizational unit
  • All relationships pointed directly to Guild

After:

  • Organization entity is the primary organizational unit
  • WowGuild entity stores WoW-specific guild data
  • One-to-one relationship: OrganizationWowGuild
  • All domain logic references organizationId

Database Schema

model Organization {
id String @id @default(uuid())
type String // 'guild', 'team', etc.
name String
guild WowGuild?
memberships Membership[]
roles Role[]
events Event[]
// ... other relations
}

model WowGuild {
id String @id @default(uuid())
organizationId String @unique
organization Organization @relation(fields: [organizationId], references: [id])
name String
realm String
faction String
syncSource String
// ... WoW-specific fields
}

API Changes

Endpoint Migration

All API endpoints have been updated from /guilds/:guildId/* to /organizations/:organizationId/*:

Before:

GET  /api/v1/guilds/:guildId/events
POST /api/v1/guilds/:guildId/memberships
GET /api/v1/guilds/:guildId/roles

After:

GET  /api/v1/organizations/:organizationId/events
POST /api/v1/organizations/:organizationId/memberships
GET /api/v1/organizations/:organizationId/roles

Backend Controllers

Controllers have been updated to use organization-scoped routes:

// membership.controller.ts
@Controller('organizations/:guildId/memberships')
export class MembershipController { ... }

// role.controller.ts
@Controller('organizations/:guildId/roles')
export class RoleController { ... }

// event.controller.ts
@Controller()
export class EventController {
@Get('organizations/:guildId/events')
findByGuild(...) { ... }

@Get('events/:id')
findOne(...) { ... }
}

Frontend Changes

Route Aliasing

The frontend maintains both /guilds/:guildId/* and /organizations/:organizationId/* routes for backward compatibility:

  • User-facing routes: /guilds/:guildId/*
  • Internal organization routes: /organizations/:organizationId/*
  • Routes automatically redirect between formats

Parameter Aliasing Hook

// useOrganizationId.ts
export function useOrganizationId() {
const params = useParams({
from: '/organizations/$organizationId',
});
return params.organizationId;
}

This hook is used throughout components to consistently access the organization ID regardless of which route format was used.

API Integration

RTK Query slices use organizationId internally:

// organizationsApi.ts
getGuildEvents: builder.query({
query: (organizationId) => `/organizations/${organizationId}/events`,
providesTags: (result, error, organizationId) => [
{ type: 'Event', id: 'LIST' },
{ type: 'Organization', id: organizationId },
],
}),

Components alias the parameter for backward compatibility:

function EventsList() {
const organizationId = useOrganizationId();
const { data: events } = useGetGuildEventsQuery(organizationId);
// Use as guildId for prop passing
const guildId = organizationId;
return <EventCard guildId={guildId} />;
}

Test Changes

Unit Tests

All unit tests have been updated to:

  • Use organizationId in mock data
  • Reference prisma.organization instead of prisma.guild
  • Expect organizationId in repository calls
  • Mock organization: { findFirst } for PrismaService

E2E Tests

E2E tests have been updated to:

  • Use /organizations/:organizationId/* routes
  • Set scope and visibility fields on events (required for visibility filtering)
  • Use the updated createTestGuild helper which creates both Organization and WowGuild

Example:

// event.e2e-spec.ts
await prisma.event.create({
data: {
organizationId: guild.id,
type: 'raid',
title: 'Raid Night',
scheduledAt: new Date('2026-03-01T19:00:00Z'),
createdBy: testUserId,
status: 'published',
scope: 'guild', // Required
visibility: 'public', // Required
},
});

const response = await request(app.getHttpServer())
.get(`/organizations/${guild.id}/events`)
.expect(200);

Migration Benefits

  1. Extensibility: Platform can now support multiple organization types
  2. Type Safety: TypeScript enforces correct usage of organizationId vs guildId
  3. Separation of Concerns: WoW-specific logic isolated in WowGuild entity
  4. Backward Compatibility: Frontend routes continue to work with /guilds/... URLs
  5. Clean Architecture: Domain logic references generic organizations, not guild-specific data

Future Work

  • Add support for additional organization types (teams, communities)
  • Implement organization-type-specific features through polymorphism
  • Consider adding organization settings that vary by type
  • Backend route aliasing for /guilds/... endpoints (currently frontend-only)

Testing Status

  • ✅ All unit tests passing (API: 257 tests, Web: 42 tests)
  • ✅ All e2e tests passing (48 tests across 4 suites)
  • ✅ TypeScript compilation successful
  • ✅ No linter errors