All files / realm/blong-gateway/adapter/db gatewaySubscriptionMerge.ts

100% Statements 107/107
30% Branches 3/10
100% Functions 1/1
100% Lines 107/107

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 1081x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 3x 3x 3x 3x 3x 1x  
import {type IMeta, handler} from '@feasibleone/blong';
 
import {newUuid, uuidBuf} from './gatewayUuid.ts';
 
type KnexQb = any;
 
/**
 * Create/merge subscriptions linking an application to a bundle.
 *
 * Wire: `gateway.subscription.merge` — besides the `gateway_subscription` row,
 * creates the `application hasRole bundleRole` core.triple edge and refreshes
 * `access_pathRefresh`, so the application's effective actions (and hence its
 * token authorization) follow from the subscribed bundle.  The graph edge +
 * refresh go through the shared `core.triple.merge` helper (P3).
 */
export default handler(
    ({
        handler: {
            'db/accessRoleEnsure': accessRoleEnsure,
            'db/coreResourceEnsure': coreResourceEnsure,
            'db/coreTripleMerge': coreTripleMerge,
        },
    }) =>
        async function gatewaySubscriptionMerge(
            params: {
                subscription?: Record<
                    string,
                    {
                        /** Application clientId. */
                        application?: string;
                        /** Bundle name. */
                        bundle?: string;
                        status?: string;
                        startsAt?: string;
                        endsAt?: string;
                    }
                >;
            },
            $meta: IMeta,
        ): Promise<{success: boolean}> {
            const qb: KnexQb = this.config?.context?.queryBuilder;
            if (!qb) throw new Error('Database not available');
 
            if (!params.subscription) return {success: true};
 
            const triples: Array<{subjectId: string; predicateName: string; objectId: string}> = [];
 
            for (const [, subDef] of Object.entries(params.subscription)) {
                // Resolve the application resource (by clientId).
                const {resourceId: applicationId} = await coreResourceEnsure<{resourceId: string}>(
                    {
                        name: subDef.application ?? '',
                        typeAlias: 'gateway.application',
                        table: 'gateway_application',
                        extraColumns: {
                            ownerUserId: null,
                            applicationType: 'oauth2_client',
                            description: 'Demo API consumer',
                            isActive: 1,
                        },
                        keyName: 'applicationId',
                    },
                    $meta,
                );
 
                // The bundle's role IS the bundle resource (bundleId === roleId).
                // A role the seed names must exist: the bit is allocated when it
                // is new (it used to be hardcoded to 0 and silently skipped).
                const {role: ensuredRole} = await accessRoleEnsure<{role: {roleId: string}}>(
                    {
                        role: {
                            roleName: subDef.bundle ?? '',
                            description: `${subDef.bundle} bundle role`,
                        },
                    },
                    $meta,
                );
                const roleId = ensuredRole.roleId;
 
                // Subscription row — upsert on (applicationId, bundleId) so the
                // seed is idempotent across server restarts.
                await qb('gateway_subscription')
                    .insert({
                        subscriptionId: uuidBuf(newUuid()),
                        applicationId: uuidBuf(applicationId),
                        bundleId: uuidBuf(roleId),
                        status: subDef.status ?? 'active',
                        startsAt: new Date(subDef.startsAt ?? '2000-01-01'),
                        endsAt: subDef.endsAt ? new Date(subDef.endsAt) : null,
                        createdAt: new Date(),
                    })
                    .onConflict(['applicationId', 'bundleId'])
                    .merge();
 
                // Authorization edge: application hasRole bundle role
                triples.push({
                    subjectId: applicationId,
                    predicateName: 'hasRole',
                    objectId: roleId,
                });
            }
 
            await coreTripleMerge({triples, refreshPath: true}, $meta);
 
            return {success: true};
        },
);