All files / realm/blong-access/adapter/db accessRoleAdd.ts

83.07% Statements 54/65
50% Branches 5/10
50% Functions 1/2
83.07% Lines 54/65

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 661x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 7x 7x 7x 7x 7x 7x 7x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 2x 2x 3x 3x                       2x 2x 2x 2x 2x 3x 3x 3x 3x 7x 1x  
import {type IMeta, handler} from '@feasibleone/blong';
 
import * as account from './account.ts';
import * as model from './accessModel.ts';
 
type KnexQb = any;
 
/**
 * `access.role.add` — create a role (resource + `access_role` row) plus its
 * record-level ACL.
 *
 * Reuses `core.resource.ensure` to create the resource-backed row (the generic
 * knex `add` cannot — the PK is `uidNotNull`, not `uuid()`). When the form
 * submitted a `matrix` (the tri-state scope × CRUD grid of the Access tab), the
 * `access_acl` rules of the new role are written by `syncAclMatrix`.
 */
export default handler(
    ({
        handler: {
            'db/accessRoleEnsure': accessRoleEnsure,
            'db/coreResourceEnsure': coreResourceEnsure,
        },
        lib: {ulid, crockfordDecode},
    }) => ({
        async accessRoleAdd(
            params: {
                role?: {roleName?: string; roleBit?: number | string | null; description?: string};
                matrix?: model.AclMatrixRow[];
            },
            $meta: IMeta,
        ): Promise<Record<string, unknown>> {
            const qb: KnexQb = this.config?.context?.queryBuilder;
            if (!qb) throw new Error('Database not available');
            // `access.role.ensure` allocates the bit when the form leaves it
            // blank and refuses one taken by another role — the returned bit is
            // the one the role actually owns (a bit never moves).
            const {role: created} = await accessRoleEnsure<model.EnsuredRole>(
                {role: params.role},
                $meta,
            );
            const roleHex = model.binHex(created.roleId);
            if (!roleHex) throw new Error('Could not resolve role resource id');
            if (Array.isArray(params.matrix) && params.matrix.length) {
                await model.syncAclMatrix(
                    qb,
                    {
                        coreResourceEnsure,
                        newAclId: () => Buffer.from(crockfordDecode(ulid())),
                    },
                    roleHex,
                    params.matrix,
                    $meta,
                );
            }
            return {
                role: {
                    roleId: model.bufToBase64(account.uuidBuf(created.roleId)),
                    roleName: created.roleName,
                    roleBit: created.roleBit,
                    description: params.role?.description ?? null,
                },
            };
        },
    }),
);