Source: Router/user.js

// @ts-check
/**
 * @import {ExpressRequestAuthorized, ExpressResponse} from '../types.js'
 */

import express from 'express';
import { checkBaseAdmin, checkAdmin } from '../utils/authChecks.js';
import { requestUpdateLogger } from '../utils/requestLogger.js';
import * as userController from './user/controller.js';

/** @type {express.Express} */
const api = express();

/**
 * User Router
 *
 * Manages the lifecycle of **identifyer links** - the join records that connect
 * a Keycloak account (UID) to a CommTool person/guest/extern object.
 * Also handles org-wide and batch user sync, full-text user search,
 * user-cache invalidation, and the portal-pending flow used for out-of-band
 * account linking via email token.
 *
 * @swagger
 * tags:
 *   - name: User
 *     description: >
 *       Identifyer link management, user sync, search, cache invalidation,
 *       and portal pending link lifecycle.
 *
 * components:
 *   schemas:
 *     UserObject:
 *       type: object
 *       description: Person/guest/extern member object returned with user context
 *       properties:
 *         UID:
 *           type: string
 *           format: uuid
 *           example: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *         Display:
 *           type: string
 *           example: Jane Doe
 *         Type:
 *           type: string
 *           enum: [person, guest, extern, job]
 *           example: person
 *         Title:
 *           type: string
 *           example: Jane
 *         Data:
 *           type: object
 *           description: Arbitrary member JSON data
 *           example:
 *             UIDuser: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *             email: jane@example.com
 *     UserSearchResult:
 *       type: object
 *       description: One candidate returned by the user search endpoint
 *       properties:
 *         title:
 *           type: string
 *           description: Combined ObjectBase.Title + Member.Display
 *           example: Jane Jane Doe
 *         value:
 *           type: string
 *           format: uuid
 *           description: ObjectBase UID of the person record
 *           example: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *         key:
 *           type: integer
 *           description: 0-based row index used by UI selectors
 *           example: 0
 *         type:
 *           type: string
 *           enum: [person, guest, extern]
 *           example: person
 *         UIDuser:
 *           type: string
 *           format: uuid
 *           description: Keycloak identifyer UID linked to this person
 *           example: b1c2d3e4-5678-90ab-cdef-1234567890ab
 *     PortalPendingResult:
 *       type: object
 *       description: Portal pending link row with Member data
 *       properties:
 *         UID:
 *           type: string
 *           format: uuid
 *           description: Person UID the pending link points to
 *           example: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *         Display:
 *           type: string
 *           example: Jane Doe
 *         Data:
 *           type: object
 *           description: Full Member.Data including _portal* token fields
 *           example:
 *             email: jane@example.com
 *             _portalToken: a1b2c3d4e5f6abcd
 *             _portalExpiry: 1741651200
 *             _portalLastSent: 1741564800
 *             _portalSentCount: 2
 *     SuccessResponse:
 *       type: object
 *       properties:
 *         success:
 *           type: boolean
 *           example: true
 *     ErrorResponse:
 *       type: object
 *       properties:
 *         success:
 *           type: boolean
 *           example: false
 *         message:
 *           type: string
 *           example: person with this UID not found
 */

/**
 * @swagger
 * /api/user/{person}/{identifyer}:
 *   put:
 *     summary: Assign a Keycloak identifyer to a person
 *     description: >
 *       Updates  for the given person and atomically
 *       rebuilds the identifyer link + self-visibility rows.  Logs the request
 *       via requestUpdateLogger.  The identifyer UUID must be a valid v1 UUID that
 *       corresponds to an existing Keycloak sub (kcUID).
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: person
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Person ObjectBase UID
 *         example: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *       - in: path
 *         name: identifyer
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Keycloak subject UUID to link to the person
 *         example: b1c2d3e4-5678-90ab-cdef-1234567890ab
 *     responses:
 *       200:
 *         description: Identifyer updated and links rebuilt
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/SuccessResponse'
 *             example:
 *               success: true
 *       300:
 *         description: Validation error (invalid UUID or person not found)
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 *             examples:
 *               invalidUUID:
 *                 summary: Malformed identifyer UUID
 *                 value:
 *                   success: false
 *                   message: invalid login indentifyer supplied in body
 *               notFound:
 *                 summary: Person not found
 *                 value:
 *                   success: false
 *                   message: person with this UID not found
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.put('/:person/:identifyer', requestUpdateLogger, checkAdmin, userController.updatePersonIdentifyerController);

/**
 * @swagger
 * /api/user/{person}:
 *   put:
 *     summary: Rebuild identifyer link from Member.Data.UIDuser
 *     description: >
 *       Reads  for the given person and re-creates the
 *       identifyer link and self-visibility row without changing the stored
 *       UIDuser value.  Useful to repair broken link state after migrations or
 *       manual DB edits.
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: person
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Person ObjectBase UID
 *         example: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *     responses:
 *       200:
 *         description: Link rebuilt (or error details if person not found / no UIDuser)
 *         content:
 *           application/json:
 *             schema:
 *               oneOf:
 *                 - : '#/components/schemas/SuccessResponse'
 *                 - : '#/components/schemas/ErrorResponse'
 *             examples:
 *               ok:
 *                 summary: Link rebuilt
 *                 value:
 *                   success: true
 *               notFound:
 *                 summary: Person does not exist
 *                 value:
 *                   success: false
 *                   message: person with this UID not found
 *               noUIDuser:
 *                 summary: Member.Data has no UIDuser field
 *                 value:
 *                   success: false
 *                   message: person has no UIDuser in Member.Data
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.put('/:person', checkAdmin, userController.putUserController);

/**
 * @swagger
 * /api/user/sync/{UIDsource}:
 *   post:
 *     summary: Sync all identifyer links for an organisation source group
 *     description: >
 *       Performs a full replace-sync of identifyer links and self-visibility rows
 *       for **every** person/guest/extern/job that is a member of the given source
 *       group.  The operation runs in a single DB transaction:
 *       1. Deletes all existing identifyer links for the orga.
 *       2. Deletes all self-visibility rows for the orga.
 *       3. Re-inserts identifyer links from .
 *       4. Re-inserts self-visibility rows.
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: UIDsource
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Source group ObjectBase UID
 *         example: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *     responses:
 *       200:
 *         description: Sync completed
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/SuccessResponse'
 *             example:
 *               success: true
 *       300:
 *         description: Precondition failed
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 *             examples:
 *               noPersons:
 *                 summary: No persons found for the source group
 *                 value:
 *                   success: false
 *                   message: no persons found for this organisation
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.post('/sync/:UIDsource', checkAdmin, userController.syncBySourceController);

/**
 * @swagger
 * /api/user:
 *   post:
 *     summary: Sync identifyer links for a selected list of person UIDs
 *     description: >
 *       Rebuilds identifyer links and self-visibility rows for the supplied list of
 *       person UIDs in a single transaction.  Useful for partial syncs after
 *       Keycloak import of specific accounts.
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - users
 *             properties:
 *               users:
 *                 type: array
 *                 description: Array of person ObjectBase UIDs to sync
 *                 minItems: 1
 *                 items:
 *                   type: string
 *                   format: uuid
 *                 example:
 *                   - aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *                   - b1c2d3e4-5678-90ab-cdef-1234567890ab
 *     responses:
 *       200:
 *         description: Sync completed or validation error
 *         content:
 *           application/json:
 *             schema:
 *               oneOf:
 *                 - : '#/components/schemas/SuccessResponse'
 *                 - : '#/components/schemas/ErrorResponse'
 *             examples:
 *               ok:
 *                 summary: Successfully synced
 *                 value:
 *                   success: true
 *               badBody:
 *                 summary: Missing or invalid users array
 *                 value:
 *                   success: false
 *                   message: body has to contain an object of {users:[...]}
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.post('/', checkAdmin, userController.syncUsersController);

/**
 * @swagger
 * /api/user/{UIDperson}:
 *   delete:
 *     summary: Remove identifyer link and self-visibility for one person
 *     description: >
 *       Deletes the persisted identifyer link (Links.Type = 'identifyer') whose
 *       UIDTarget points to the given person, and removes all Visible rows where
 *       UIDUser = person.  This effectively logs the person out of all sessions
 *       and prevents future Keycloak authentication.
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: UIDperson
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Person ObjectBase UID
 *         example: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *     responses:
 *       200:
 *         description: Identifyer and visibility rows deleted
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/SuccessResponse'
 *             example:
 *               success: true
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.delete('/:UIDperson', checkAdmin, userController.deleteUserController);

/**
 * @swagger
 * /api/user:
 *   delete:
 *     summary: Bulk-delete identifyer links and visibility for selected users
 *     description: >
 *       Same as DELETE /api/user/{UIDperson} but for a batch of persons in a
 *       single query.  Requires a non-empty  array in the request body.
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - users
 *             properties:
 *               users:
 *                 type: array
 *                 description: Person ObjectBase UIDs to de-authenticate
 *                 minItems: 1
 *                 items:
 *                   type: string
 *                   format: uuid
 *                 example:
 *                   - aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *                   - b1c2d3e4-5678-90ab-cdef-1234567890ab
 *     responses:
 *       200:
 *         description: Deleted or validation error
 *         content:
 *           application/json:
 *             schema:
 *               oneOf:
 *                 - : '#/components/schemas/SuccessResponse'
 *                 - : '#/components/schemas/ErrorResponse'
 *             examples:
 *               ok:
 *                 summary: Deleted
 *                 value:
 *                   success: true
 *               badBody:
 *                 summary: Missing or empty users array
 *                 value:
 *                   success: false
 *                   message: body has to contain an object of {users:[...]}
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.delete('/', checkAdmin, userController.deleteUsersController);

/**
 * @swagger
 * /api/user/invalidate-cache:
 *   post:
 *     summary: Invalidate the user cache for one, many, or all users
 *     description: >
 *       Intended for bots and automation that perform Keycloak syncs and need
 *       to flush stale session / permission caches without restarting the server.
 *       Accepts three mutually exclusive body shapes:
 *       -  - single user by UUID string
 *       -  - array of UUID strings
 *       -  - all users in the organisation (use with caution)
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             oneOf:
 *               - type: object
 *                 required: [user]
 *                 properties:
 *                   user:
 *                     type: string
 *                     format: uuid
 *                     description: Single user UUID to invalidate
 *                     example: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *               - type: object
 *                 required: [users]
 *                 properties:
 *                   users:
 *                     type: array
 *                     description: Multiple user UUIDs to invalidate
 *                     items:
 *                       type: string
 *                       format: uuid
 *                     example:
 *                       - aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *                       - b1c2d3e4-5678-90ab-cdef-1234567890ab
 *               - type: object
 *                 required: [all]
 *                 properties:
 *                   all:
 *                     type: boolean
 *                     enum: [true]
 *                     description: Invalidate all users for the current organisation
 *                     example: true
 *     responses:
 *       200:
 *         description: Cache invalidated
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 invalidated:
 *                   type: integer
 *                   description: Number of users invalidated (-1 means all)
 *                   example: 2
 *             examples:
 *               single:
 *                 summary: Single user invalidated
 *                 value:
 *                   success: true
 *                   invalidated: 1
 *               batch:
 *                 summary: Batch of users invalidated
 *                 value:
 *                   success: true
 *                   invalidated: 3
 *               all:
 *                 summary: All users invalidated
 *                 value:
 *                   success: true
 *                   invalidated: -1
 *       400:
 *         description: Invalid request body shape
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 *             example:
 *               success: false
 *               message: >
 *                 Body must contain { user: UUID-... } or { users: [...] } or { all: true }
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.post('/invalidate-cache', checkAdmin, userController.invalidateCacheController);

/**
 * @swagger
 * /api/user/SearchData:
 *   get:
 *     summary: Full-text search for user candidates
 *     description: >
 *       Searches  in BOOLEAN MODE for persons, guests, and
 *       externs that have an identifyer link (i.e. are active Keycloak users).
 *       Returns a compact list suitable for autocomplete / select-box widgets.
 *       Results are sorted by  ascending and grouped by member UID.
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: search
 *         required: true
 *         schema:
 *           type: string
 *         description: MySQL BOOLEAN MODE full-text search expression
 *         example: jane
 *     responses:
 *       200:
 *         description: Matching user candidates
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 : '#/components/schemas/UserSearchResult'
 *             example:
 *               - title: Jane Jane Doe
 *                 value: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *                 key: 0
 *                 type: person
 *                 UIDuser: b1c2d3e4-5678-90ab-cdef-1234567890ab
 *               - title: Doctor Jane Smith
 *                 value: c3d4e5f6-7890-12ab-cdef-234567890abc
 *                 key: 1
 *                 type: extern
 *                 UIDuser: d4e5f6a7-8901-23bc-def0-34567890abcd
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.get('/SearchData', checkBaseAdmin, userController.getSearchDataController);

/**
 * @swagger
 * /api/user/person/{UIDperson}:
 *   get:
 *     summary: Resolve the Keycloak identifyer UID for a person
 *     description: >
 *       Looks up  for a row where  and  *       and returns the linked , which is the Keycloak subject UUID.
 *       Returns 404 if no link exists (person is not yet a Keycloak user).
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: UIDperson
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Person ObjectBase UID
 *         example: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *     responses:
 *       200:
 *         description: Identifyer UID resolved
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 result:
 *                   type: string
 *                   format: uuid
 *                   description: Keycloak subject UUID
 *                   example: b1c2d3e4-5678-90ab-cdef-1234567890ab
 *       404:
 *         description: No identifyer link found for this person
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 *             example:
 *               success: false
 *               message: no identifyer found for person
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.get('/person/:UIDperson', checkAdmin, userController.getPersonIdentifyerController);

/**
 * @swagger
 * /api/user/{UIDuser}:
 *   get:
 *     summary: Get member details for a Keycloak identifyer UID
 *     description: >
 *       Returns the person/guest/extern/job member object that is linked to the
 *       given Keycloak subject UUID () within the
 *       current organisation ().  Includes full Member.Data.
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: UIDuser
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Keycloak subject UUID (identifyer UID)
 *         example: b1c2d3e4-5678-90ab-cdef-1234567890ab
 *     responses:
 *       200:
 *         description: User record found or not found in this orga
 *         content:
 *           application/json:
 *             schema:
 *               oneOf:
 *                 - type: object
 *                   properties:
 *                     success:
 *                       type: boolean
 *                       example: true
 *                     result:
 *                       : '#/components/schemas/UserObject'
 *                 - : '#/components/schemas/ErrorResponse'
 *             examples:
 *               found:
 *                 summary: User found in the current organisation
 *                 value:
 *                   success: true
 *                   result:
 *                     UID: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *                     Display: Jane Doe
 *                     Type: person
 *                     Title: Jane
 *                     Data:
 *                       UIDuser: b1c2d3e4-5678-90ab-cdef-1234567890ab
 *                       email: jane@example.com
 *               notFound:
 *                 summary: User not in current organisation
 *                 value:
 *                   success: false
 *                   message: user not found in this orga
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.get('/:UIDuser', checkAdmin, userController.getUserByUIDController);

/**
 * @swagger
 * /api/user:
 *   get:
 *     summary: List all identifyer link UIDs
 *     description: >
 *       Returns every row in  where .
 *       Intended for admin/diagnostic tooling.  The list is not paginated and
 *       may be large in production environments.
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: All identifyer link UIDs
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                   example: true
 *                 result:
 *                   type: array
 *                   items:
 *                     type: object
 *                     properties:
 *                       UIDidentifyer:
 *                         type: string
 *                         format: uuid
 *                         description: Keycloak identifyer UID
 *                         example: b1c2d3e4-5678-90ab-cdef-1234567890ab
 *                       UIDuser:
 *                         type: string
 *                         format: uuid
 *                         description: CommTool person ObjectBase UID
 *                         example: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *             example:
 *               success: true
 *               result:
 *                 - UIDidentifyer: b1c2d3e4-5678-90ab-cdef-1234567890ab
 *                   UIDuser: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *                 - UIDidentifyer: d4e5f6a7-8901-23bc-def0-34567890abcd
 *                   UIDuser: c3d4e5f6-7890-12ab-cdef-234567890abc
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.get('/', checkAdmin, userController.getAllIdentifyersController);





/**
 * @swagger
 * /api/user/pending/{person}/{kcUID}:
 *   put:
 *     summary: Create or refresh a portal pending link
 *     description: >
 *       Used by the portal email-linking flow.  Creates (or replaces) a
 *        row that maps a Keycloak UID to a person
 *       UID, and stores the hashed token + expiry + audit fields in
 *       .  The entire operation is atomic.
 *
 *       -        = kcUID (Keycloak sub, binary 16)
 *       -       =  *       -  = person UID
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: person
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Person ObjectBase UID
 *         example: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *       - in: path
 *         name: kcUID
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Keycloak subject UUID for the pending link
 *         example: b1c2d3e4-5678-90ab-cdef-1234567890ab
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               tokenHash:
 *                 type: string
 *                 description: Hashed email-link token (hex or base64)
 *                 example: a1b2c3d4e5f6abcdef1234567890abcd
 *               expiry:
 *                 type: integer
 *                 description: Unix timestamp of token expiry
 *                 example: 1741651200
 *               lastSent:
 *                 type: integer
 *                 description: Unix timestamp of the last send
 *                 example: 1741564800
 *               sentCount:
 *                 type: integer
 *                 description: Number of times the email has been sent (default 1)
 *                 example: 1
 *           example:
 *             tokenHash: a1b2c3d4e5f6abcdef1234567890abcd
 *             expiry: 1741651200
 *             lastSent: 1741564800
 *             sentCount: 1
 *     responses:
 *       200:
 *         description: Portal pending link created or refreshed
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/SuccessResponse'
 *             example:
 *               success: true
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.put('/pending/:person/:kcUID', checkAdmin, userController.upsertPortalPendingController);

/**
 * @swagger
 * /api/user/pending/{kcUID}:
 *   get:
 *     summary: Look up a portal pending link by Keycloak UID
 *     description: >
 *       Returns the Member data of the person that has an outstanding
 *        link to the given Keycloak sub UUID within the current
 *       organisation.  The full  is returned including the
 *        token fields so the caller can validate the token.
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: kcUID
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Keycloak subject UUID of the pending link
 *         example: b1c2d3e4-5678-90ab-cdef-1234567890ab
 *     responses:
 *       200:
 *         description: Pending link found or not found
 *         content:
 *           application/json:
 *             schema:
 *               oneOf:
 *                 - type: object
 *                   properties:
 *                     success:
 *                       type: boolean
 *                       example: true
 *                     result:
 *                       : '#/components/schemas/PortalPendingResult'
 *                 - : '#/components/schemas/ErrorResponse'
 *             examples:
 *               found:
 *                 summary: Pending link found
 *                 value:
 *                   success: true
 *                   result:
 *                     UID: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *                     Display: Jane Doe
 *                     Data:
 *                       email: jane@example.com
 *                       _portalToken: a1b2c3d4e5f6abcdef1234567890abcd
 *                       _portalExpiry: 1741651200
 *                       _portalLastSent: 1741564800
 *                       _portalSentCount: 1
 *               notFound:
 *                 summary: No pending link for this KC UID in the current org
 *                 value:
 *                   success: false
 *                   message: No pending link found for this organization
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.get('/pending/:kcUID', checkAdmin, userController.getPortalPendingByKcUIDController);

/**
 * @swagger
 * /api/user/pending/{person}:
 *   delete:
 *     summary: Remove a portal pending link and clean up token metadata
 *     description: >
 *       Atomically deletes the  Links row whose UIDTarget is the
 *       given person, and removes the , ,
 *       , and  fields from .
 *       Called by the portal after successful token validation, immediately
 *       before activating the permanent identifyer link.
 *     tags: [User]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: person
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *         description: Person ObjectBase UID
 *         example: aefe03ad-3d74-11f0-b612-b2b0f54f4439
 *     responses:
 *       200:
 *         description: Portal pending link and token fields removed
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/SuccessResponse'
 *             example:
 *               success: true
 *       500:
 *         description: Unexpected server error
 *         content:
 *           application/json:
 *             schema:
 *               : '#/components/schemas/ErrorResponse'
 */
// @ts-ignore
api.delete('/pending/:person', checkAdmin, userController.deletePortalPendingController);

export default api;