Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Codefied-CodePix/Karokar-backend/llms.txt

Use this file to discover all available pages before exploring further.

KaroKar operates as a B2B2C mobility marketplace where Vendor organizations supply vehicles, Corporate organizations book and assign them to employees, and the KaroKar Platform organization governs the entire ecosystem. Rather than maintaining separate tables for each participant type, the platform consolidates all of them into a single unified Organization model — enabling consistent onboarding, authorization, and reporting across every tenant, while keeping the door open for future participant types such as Fleet Operators, Insurance Partners, and Government Agencies.

The Unified Organization Model

Every tenant in KaroKar is an Organization record. The OrganizationType enum determines what capabilities that organization holds within the platform.
// src/identity/domain/enums/organization-type.enum.ts
export enum OrganizationType {
  PLATFORM  = 'PLATFORM',   // KaroKar itself — system owner
  VENDOR    = 'VENDOR',     // Vehicle providers
  CORPORATE = 'CORPORATE',  // Vehicle consumers (employers)
}
// src/identity/domain/enums/organization-status.enum.ts
export enum OrganizationStatus {
  PENDING   = 'PENDING',    // Awaiting platform review
  ACTIVE    = 'ACTIVE',     // Fully operational
  SUSPENDED = 'SUSPENDED',  // Temporarily blocked
  REJECTED  = 'REJECTED',   // Denied by platform admin
}
The Organization entity itself is intentionally lean. Business-domain-specific metadata is stored in the flexible metadata: Record<string, unknown> JSONB column so that the schema does not need to change as the platform grows.
// src/identity/domain/entities/organization.entity.ts
@Entity('organizations')
export class Organization extends BaseEntity {
  @Column()
  name!: string;

  @Column({ type: 'enum', enum: OrganizationType })
  type!: OrganizationType;

  @Column({ type: 'enum', enum: OrganizationStatus })
  status!: OrganizationStatus;

  @Column({ name: 'parent_organization_id', type: 'uuid', nullable: true })
  parentOrganizationId!: string | null;

  @Column({ type: 'jsonb', default: {} })
  metadata!: Record<string, unknown>;
}
The parentOrganizationId field is present but nullable in Phase 01. It is reserved for future enterprise hierarchy support — for example, grouping ABC Logistics, ABC Transport, and ABC Holdings under a single ABC Group parent organization without requiring any schema changes.

User Membership Model

Users do not hold roles globally — they earn them through organization memberships. The OrganizationMember entity is the join between a user account, an organization, and the role that governs what that user may do inside that organization.
// src/identity/domain/entities/organization-member.entity.ts
@Entity('organization_members')
export class OrganizationMember {
  @PrimaryGeneratedColumn('uuid')
  id!: string;

  @Column({ name: 'organization_id', type: 'uuid' })
  organizationId!: string;

  @Column({ name: 'user_id', type: 'uuid' })
  userId!: string;

  @Column({ name: 'role_id', type: 'uuid' })
  roleId!: string;

  @Column({ type: 'enum', enum: MemberStatus })
  status!: MemberStatus;

  @Column({ name: 'joined_at', type: 'timestamptz' })
  joinedAt!: Date;
}
The MemberStatus enum governs the lifecycle state of a membership record independently of the organization’s own status:
// src/identity/domain/enums/member-status.enum.ts
export enum MemberStatus {
  ACTIVE    = 'ACTIVE',
  INACTIVE  = 'INACTIVE',
  SUSPENDED = 'SUSPENDED',
}
This design means a single user can hold different roles in different organizations simultaneously — for instance, a consultant who acts as a Vendor Admin in ABC Rentals and a Corporate Admin in XYZ Holdings at the same time.

Phase 01 Roles

PLATFORM_ADMIN

Belongs to the KaroKar Platform organization. Approves onboarding, reviews verifications, and manages global compliance.

VENDOR_ADMIN

Belongs to a Vendor organization. Manages the vehicle fleet, reviews and approves booking requests from Corporate organizations.

CORPORATE_ADMIN

Belongs to a Corporate organization. Creates bookings, manages employee memberships, and assigns vehicles to staff.

EMPLOYEE

Belongs to a Corporate organization. Views their own assignments, accepts or rejects trips, and reads booking details relevant to them.

Data Ownership Rules

Every domain entity carries an organizationId (or equivalent ownership column) so that tenant boundaries are enforced at the data level, not just in application code.
Domain EntityOwnership ColumnBelongs To
VehicleorganizationIdVendor organization
BookingcorporateOrganizationId + vendorOrganizationIdCorporate (requester) + Vendor (fulfiller)
AssignmentcorporateOrganizationIdCorporate organization
VerificationorganizationIdThe organization being verified
Bookings are the one entity that carries two organization references, because they represent a cross-tenant transaction: a Corporate organization requesting a vehicle from a Vendor organization.

Tenant Isolation Strategy

Phase 01 uses logical tenant isolation: all organizations share the same application process, database, and infrastructure. Isolation is enforced exclusively through organizationId filtering on every query. No row escapes its tenant boundary without an explicit cross-tenant query, and those are only permitted within tightly controlled service methods.
// Conceptual query pattern enforced throughout all repository layers
SELECT * FROM vehicles
WHERE organization_id = :currentOrganizationId;
The OrganizationContextInterceptor extracts the active organizationId from the authenticated JWT payload and attaches it to the request object, making it available to all downstream guards, services, and repositories within a single request lifecycle.
// src/shared/interceptors/organization-context.interceptor.ts
@Injectable()
export class OrganizationContextInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const request = context.switchToHttp().getRequest<AuthenticatedRequest>();
    const user = request.user as JwtPayload | undefined;

    if (user?.organizationId) {
      request.organizationId = user.organizationId;

      if (!('userId' in user) || !user.userId) {
        request.user = {
          userId: user.sub ?? user.userId ?? '',
          organizationId: user.organizationId,
        };
      }
    }

    return next.handle();
  }
}

Why Not Separate Vendor and Corporate Tables?

The alternative of maintaining distinct Vendor and Corporate tables was considered and rejected. Separate tables would duplicate onboarding flows, permission models, and reporting pipelines for every new participant type. The unified Organization model means that adding an INSURANCE_PARTNER type in a future phase is a single enum addition — no schema migrations, no new repositories, no new onboarding controllers.

Build docs developers (and LLMs) love