All files / src/features/white-labeling white-labeling.mapper.ts

72.72% Statements 40/55
73.68% Branches 84/114
100% Functions 4/4
80.43% Lines 37/46

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161                          9x         9x                                 9x                               9x                         9x             5x         5x 4x 4x 4x 4x 4x 4x   4x       5x 2x 1x 1x 1x       2x 1x             5x   1x       1x   1x       5x                           5x   5x   5x               7x 7x   7x 10x   8x       8x   10x     7x      
import { Configuration, Prisma } from '@prisma/client'
import { EventConfig, defaultConfig, DeepPartial, deepMerge } from '@fundraising/white-labeling'
 
export class WhiteLabelingMapper {
    /**
     * Transforms database Configuration entity and optional Event entity back into the modular EventConfig structure.
     */
    static toEventConfig(
        config: Configuration,
        event?: any,
        baseConfig?: EventConfig,
    ): EventConfig {
        // 1. Initialize from base config or default config
        const result: EventConfig = baseConfig
            ? JSON.parse(JSON.stringify(baseConfig))
            : JSON.parse(JSON.stringify(defaultConfig))
 
        // 2. Map root columns to partial structure (Priority: Low)
        const rootOverrides: DeepPartial<EventConfig> = {
            id: config.entityId || undefined,
            content: config.organization ? { title: config.organization } : {},
            communication: {
                legalName: config.organization || undefined,
                address: config.address || undefined,
                phone: config.phone || undefined,
                supportEmail: config.email || undefined,
                website: config.website || undefined,
            },
            theme: {
                assets: config.logo ? { logo: config.logo } : {},
            },
            live: config.liveTheme ? { theme: config.liveTheme as any } : undefined,
        }
 
        // 3. Map JSON blobs to partial structure (Priority: Medium)
        const blobOverrides: DeepPartial<EventConfig> = {
            theme: {
                assets: (config.assets || undefined) as any,
                variables: (config.themeVariables || undefined) as any,
            },
            content: (config.event || undefined) as any,
            donation: {
                form: (config.form || undefined) as any,
                payment: (config.payment || undefined) as any,
                sharing: (config.socialNetwork || undefined) as any,
            },
            communication: (config.communication || undefined) as any,
            locales: (config.locales || undefined) as any,
        }
 
        // 4. Map dynamic Event entity props (Priority: High)
        const eventOverrides: DeepPartial<EventConfig> = event
            ? {
                  id: event.id,
                  name: event.name,
                  description: event.description || '',
                  content: {
                      title: event.name,
                      goalAmount: event.goalAmount ? Number(event.goalAmount) : undefined,
                  },
              }
            : {}
 
        // Perform single multi-source deep merge
        return deepMerge(result, rootOverrides, blobOverrides, eventOverrides) as EventConfig
    }
 
    /**
     * Transforms EventConfig structure into Database Schema columns for update/create operations.
     */
    static toDbPayload(data: DeepPartial<EventConfig>): Prisma.ConfigurationUpdateInput {
        const payload: Prisma.ConfigurationUpdateInput = {
            updatedAt: new Date(),
        }
 
        // 1. Communication -> Root Columns & JSON
        if (data.communication) {
            const c = data.communication
            Eif (c.legalName !== undefined) payload.organization = c.legalName || null
            if (c.address !== undefined) payload.address = c.address || null
            Iif (c.phone !== undefined) payload.phone = c.phone || null
            Iif (c.supportEmail !== undefined) payload.email = c.supportEmail || null
            if (c.website !== undefined) payload.website = c.website || null
 
            payload.communication = this.cleanForPersistence(c) as Prisma.InputJsonValue
        }
 
        // 2. Theme -> Assets (Logo) & Variables
        if (data.theme !== undefined) {
            if (data.theme?.assets !== undefined) {
                Eif (data.theme.assets?.logo !== undefined)
                    payload.logo = data.theme.assets.logo || null
                payload.assets = this.cleanForPersistence(
                    data.theme.assets,
                ) as Prisma.InputJsonValue
            }
            if (data.theme?.variables !== undefined) {
                payload.themeVariables = this.cleanForPersistence(
                    data.theme.variables,
                ) as Prisma.InputJsonValue
            }
        }
 
        // 3. Content -> event column
        if (data.content !== undefined) {
            // Logic: If user provides content.title and NOT communication.legalName, we sync to organization
            Eif (
                data.content?.title !== undefined &&
                (!data.communication || data.communication.legalName === undefined)
            ) {
                payload.organization = data.content.title || null
            }
            payload.event = this.cleanForPersistence(data.content) as Prisma.InputJsonValue
        }
 
        // 4. Donation -> form, payment, sharing
        Iif (data.donation !== undefined) {
            if (data.donation?.form !== undefined)
                payload.form = this.cleanForPersistence(data.donation.form) as Prisma.InputJsonValue
            if (data.donation?.payment !== undefined)
                payload.payment = this.cleanForPersistence(
                    data.donation.payment,
                ) as Prisma.InputJsonValue
            if (data.donation?.sharing !== undefined)
                payload.socialNetwork = this.cleanForPersistence(
                    data.donation.sharing,
                ) as Prisma.InputJsonValue
        }
 
        // 5. Locales & Live Theme
        Iif (data.locales)
            payload.locales = this.cleanForPersistence(data.locales) as Prisma.InputJsonValue
        Iif (data.live?.theme) payload.liveTheme = data.live.theme
 
        return payload
    }
 
    /**
     * Recursively removes empty strings, nulls, and undefineds from objects to avoid cluttering JSON columns
     * and ensuring correct inheritance behavior (where only actual values override defaults).
     */
    private static cleanForPersistence(obj: any): any {
        Iif (obj === null || obj === undefined) return Prisma.DbNull
        Iif (typeof obj !== 'object' || Array.isArray(obj)) return obj
 
        const cleaned = Object.entries(obj).reduce((acc: any, [key, value]) => {
            if (value === '' || value === null || value === undefined) {
                // Skip for inheritance
            } else Iif (typeof value === 'object') {
                const child = this.cleanForPersistence(value)
                if (child !== Prisma.DbNull) acc[key] = child
            } else {
                acc[key] = value
            }
            return acc
        }, {})
 
        return Object.keys(cleaned).length > 0 ? cleaned : Prisma.DbNull
    }
}